github.com/litesolutions/justifay-api@v1.0.0-2.0.20220707114139-46f28a909481/authorization/auth_interceptor.go (about) 1 package authorization 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strings" 8 "time" 9 10 "github.com/uptrace/bun" 11 "google.golang.org/grpc" 12 "google.golang.org/grpc/codes" 13 "google.golang.org/grpc/metadata" 14 "google.golang.org/grpc/status" 15 16 uuid "github.com/google/uuid" 17 "github.com/litesolutions/justifay-api/model" 18 "github.com/litesolutions/justifay-api/pkg/access" 19 uuidpkg "github.com/litesolutions/justifay-api/pkg/uuid" 20 pbUser "github.com/litesolutions/justifay-api/proto/user" 21 grpclog "google.golang.org/grpc/grpclog" 22 ) 23 24 var ( 25 // ErrAccessTokenNotFound ... 26 ErrAccessTokenNotFound = errors.New("Access token not found") 27 // ErrAccessTokenExpired ... 28 ErrAccessTokenExpired = errors.New("Access token expired") 29 ) 30 31 type AuthInterceptor struct { 32 db *bun.DB 33 rf int 34 acc *access.AccessConfig 35 } 36 37 func stringInSlice(a string, list []string) bool { 38 for _, b := range list { 39 if b == a { 40 return true 41 } 42 } 43 return false 44 } 45 46 func NewAuthInterceptor(db *bun.DB, rf int, acc *access.AccessConfig) *AuthInterceptor { 47 return &AuthInterceptor{db, rf, acc} 48 } 49 50 func (interceptor *AuthInterceptor) Unary() grpc.UnaryServerInterceptor { 51 return func( 52 ctx context.Context, 53 req interface{}, 54 info *grpc.UnaryServerInfo, 55 handler grpc.UnaryHandler, 56 ) (interface{}, error) { 57 var err error 58 59 start := time.Now() 60 61 // Logging with grpclog (grpclog.LoggerV2) 62 63 args := []string{ 64 "[user-api-auth]", 65 //formatOperation(event), 66 fmt.Sprintf("Request - Method:%s\tDuration:0\tError:%v\n", info.FullMethod, err), 67 } 68 69 // if event.Err != nil { 70 // typ := reflect.TypeOf(event.Err).String() 71 // args = append(args, 72 // "\t", 73 // color.New(color.BgRed).Sprintf(" %s ", typ+": "+event.Err.Error()), 74 // ) 75 // } 76 77 //fmt.Println(args...) 78 79 grpclog.Infof(strings.Join(args, "")) 80 // grpclog.Infof("Request - Method:%s\tDuration:0\tError:%v\n", 81 // info.FullMethod, 82 // err) 83 84 NoTokenMethods := strings.Split(interceptor.acc.NoTokenMethods, ",") 85 86 TokenRequired := !stringInSlice(info.FullMethod, NoTokenMethods) 87 88 // Skip authorize when configured methods are requested 89 // eg if requesting token 90 if TokenRequired { 91 grpclog.Infof("Expecting AccessToken, let's check ...") 92 err := interceptor.authorize(ctx, req, info.FullMethod) 93 if err != nil { 94 grpclog.Infof("Request Denied - Method:%s\tDuration:%s\tError:%v\n", 95 info.FullMethod, 96 time.Since(start), 97 err) 98 return nil, err 99 } 100 } 101 102 // Calls the handler 103 h, err := handler(ctx, req) 104 105 grpclog.Infof("Request Authorised - Method:%s\tDuration:%s\tError:%v\n", 106 info.FullMethod, 107 time.Since(start), 108 err) 109 110 return h, err 111 } 112 } 113 114 func (interceptor *AuthInterceptor) authorize(ctx context.Context, req interface{}, method string) error { 115 116 md, ok := metadata.FromIncomingContext(ctx) 117 if !ok { 118 return status.Errorf(codes.Unauthenticated, "metadata is not provided") 119 } 120 121 values := md["authorization"] 122 if len(values) == 0 { 123 return status.Errorf(codes.Unauthenticated, "authorization token is not provided") 124 } 125 126 PublicMethods := strings.Split(interceptor.acc.PublicMethods, ",") 127 WriteMethods := strings.Split(interceptor.acc.WriteMethods, ",") 128 129 isPublicAccessMethod := stringInSlice(method, PublicMethods) 130 131 accessTokenSource := strings.Split(values[0], " ") 132 133 if len(accessTokenSource) != 2 { 134 return status.Errorf(codes.PermissionDenied, "incorrect authorization header format") 135 } 136 137 accessToken := accessTokenSource[1] 138 139 accessTokenRecord, err := interceptor.Authenticate(accessToken) 140 if err != nil { 141 return status.Errorf(codes.Unauthenticated, "access token is invalid: %v", err) 142 } 143 144 scopes := strings.Split(accessTokenRecord.Scope, " ") 145 146 // determine if the request has write permission 147 _, read_write := interceptor.find(scopes, "read_write") 148 149 // leave now if no write permission 150 if !read_write && stringInSlice(method, WriteMethods) { 151 return status.Errorf(codes.PermissionDenied, "attempt to write to user-api without write scope") 152 } 153 154 scopes = interceptor.delete(scopes, "read_write") 155 156 scopes = interceptor.delete(scopes, "read") 157 158 // assume role is remaining scope element 159 tokenRole := scopes[0] 160 161 tokenRoleRow := new(model.Role) 162 163 err = interceptor.db.NewSelect(). 164 Model(tokenRoleRow). 165 Where("name = ?", tokenRole). 166 Scan(ctx) 167 168 if err != nil { 169 return status.Errorf(codes.PermissionDenied, "problem determining role from token") 170 } 171 172 tokenRoleValue := tokenRoleRow.ID 173 174 user := new(model.User) 175 176 err = interceptor.db.NewSelect(). 177 Model(user). 178 Where("id = ?", accessTokenRecord.UserID). 179 Scan(ctx) 180 181 if err != nil { 182 return status.Errorf(codes.PermissionDenied, "problem determining user role") 183 } 184 185 userRoleValue := user.RoleID 186 187 var activeRole int32 188 189 if userRoleValue > tokenRoleValue { 190 activeRole = userRoleValue 191 } else { 192 activeRole = tokenRoleValue 193 } 194 195 if isPublicAccessMethod { 196 // everyone can access but check it's against their own ID 197 if activeRole > int32(model.LabelRole) { 198 id, err := interceptor.extractUserIdFromReq(ctx, req, accessTokenRecord) 199 200 if err != nil { 201 return err 202 } 203 204 ID, err := uuid.Parse(id) 205 206 if err != nil { 207 return status.Errorf(codes.PermissionDenied, "UUID in request is not valid") 208 } 209 210 if ID != user.ID { 211 return status.Errorf(codes.PermissionDenied, "requestor is not authorized to take action on another user record") 212 } 213 // must be working on their own record 214 } 215 return nil 216 } 217 218 // If not an admin, you can't access the remaining non-public methods 219 if activeRole > int32(model.TenantAdminRole) { 220 return status.Errorf(codes.PermissionDenied, "requestor is not authorized for this method") 221 } 222 223 // else all is fine at this gate at least, go ahead 224 return nil 225 } 226 227 // Authenticate checks the access token is valid 228 func (interceptor *AuthInterceptor) Authenticate(token string) (*model.AccessToken, error) { 229 // Fetch the access token from the database 230 ctx := context.Background() 231 accessToken := new(model.AccessToken) 232 233 err := interceptor.db.NewSelect(). 234 Model(accessToken). 235 Where("token = ?", token). 236 Limit(1). 237 Scan(ctx) 238 239 // Not found 240 if err != nil { 241 return nil, ErrAccessTokenNotFound 242 } 243 244 // Check the access token hasn't expired 245 if time.Now().UTC().After(accessToken.ExpiresAt) { 246 return nil, ErrAccessTokenExpired 247 } 248 249 // Extend refresh token expiration database 250 251 increasedExpiresAt := time.Now().Add( 252 time.Duration(interceptor.rf) * time.Second, 253 ) 254 255 //var res sql.Result 256 257 // err = GetOrCreateRefreshToken 258 259 if uuidpkg.IsValidUUID(accessToken.UserID.String()) && accessToken.UserID != uuid.Nil { 260 _, err = interceptor.db.NewUpdate(). 261 Model(new(model.RefreshToken)). 262 Set("expires_at = ?", increasedExpiresAt). 263 Set("updated_at = ?", time.Now().UTC()). 264 Where("client_id = ?", accessToken.ClientID.String()). 265 Where("user_id = ?", accessToken.UserID.String()). 266 Exec(ctx) 267 } else { 268 _, err = interceptor.db.NewUpdate(). 269 Model(new(model.RefreshToken)). 270 Set("expires_at = ?", increasedExpiresAt). 271 Set("updated_at = ?", time.Now().UTC()). 272 Where("client_id = ?", accessToken.ClientID.String()). 273 Where("user_id = uuid_nil()"). 274 Exec(ctx) 275 } 276 277 if err != nil { 278 return nil, err 279 } 280 281 return accessToken, nil 282 } 283 284 func (interceptor *AuthInterceptor) extractUserIdFromReq(ctx context.Context, req interface{}, accessTokenRecord *model.AccessToken) (string, error) { 285 // Dealing with normal users, Label admins can maintain own artist content. 286 // attempt to extract the Id from all the possible request types dealing with failure 287 userReq, ok := req.(*pbUser.UserRequest) 288 289 if ok { 290 return userReq.Id, nil 291 } 292 293 userUpdateReq, ok := req.(*pbUser.UserUpdateRequest) 294 295 if ok { 296 return userUpdateReq.Id, nil 297 } 298 299 userGroupCreateReq, ok := req.(*pbUser.UserGroupCreateRequest) 300 301 if ok { 302 return userGroupCreateReq.Id, nil 303 } 304 305 userGroupUpdateReq, ok := req.(*pbUser.UserGroupUpdateRequest) 306 307 if ok { 308 newUserGroup := new(model.UserGroup) 309 310 err := interceptor.db.NewSelect(). 311 Model(newUserGroup). 312 Where("owner_id = ?", accessTokenRecord.UserID). 313 Where("id = ?", userGroupUpdateReq.Id). 314 Scan(ctx) 315 316 if err != nil { 317 return "", status.Errorf(codes.PermissionDenied, "Supplied UUID for User Group is not valid or logged in User doesn't own Group") 318 } 319 320 return accessTokenRecord.UserID.String(), nil 321 } 322 323 return "", status.Errorf(codes.PermissionDenied, "UUID in request is not valid") 324 } 325 326 func (interceptor *AuthInterceptor) find(slice []string, val string) (int, bool) { 327 for i, item := range slice { 328 if item == val { 329 return i, true 330 } 331 } 332 return -1, false 333 } 334 335 func (interceptor *AuthInterceptor) delete(slice []string, val string) []string { 336 var location int 337 338 for i, item := range slice { 339 if item == val { 340 location = i 341 slice = append(slice[:location], slice[location+1:]...) 342 } 343 } 344 345 return slice 346 }