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  }