package session import ( "context" "time" "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/kit/platform" "github.com/influxdata/influxdb/v2/kit/platform/errors" "github.com/influxdata/influxdb/v2/rand" "github.com/influxdata/influxdb/v2/snowflake" ) // Service implements the influxdb.SessionService interface and // handles communication between session and the necessary user and urm services type Service struct { store *Storage userService influxdb.UserService urmService influxdb.UserResourceMappingService authService influxdb.AuthorizationService sessionLength time.Duration idGen platform.IDGenerator tokenGen influxdb.TokenGenerator disableAuthorizationsForMaxPermissions func(context.Context) bool } // ServiceOption is a functional option for configuring a *Service type ServiceOption func(*Service) // WithSessionLength configures the length of the session with the provided // duration when the resulting option is called on a *Service. func WithSessionLength(length time.Duration) ServiceOption { return func(s *Service) { s.sessionLength = length } } // WithIDGenerator overrides the default ID generator with the one // provided to this function when called on a *Service func WithIDGenerator(gen platform.IDGenerator) ServiceOption { return func(s *Service) { s.idGen = gen } } // WithTokenGenerator overrides the default token generator with the one // provided to this function when called on a *Service func WithTokenGenerator(gen influxdb.TokenGenerator) ServiceOption { return func(s *Service) { s.tokenGen = gen } } // NewService creates a new session service func NewService(store *Storage, userService influxdb.UserService, urmService influxdb.UserResourceMappingService, authSvc influxdb.AuthorizationService, opts ...ServiceOption) *Service { service := &Service{ store: store, userService: userService, urmService: urmService, authService: authSvc, sessionLength: time.Hour, idGen: snowflake.NewIDGenerator(), tokenGen: rand.NewTokenGenerator(64), disableAuthorizationsForMaxPermissions: func(context.Context) bool { return false }, } for _, opt := range opts { opt(service) } return service } // WithMaxPermissionFunc sets the useAuthorizationsForMaxPermissions function // which can trigger whether or not max permissions uses the users authorizations // to derive maximum permissions. func (s *Service) WithMaxPermissionFunc(fn func(context.Context) bool) { s.disableAuthorizationsForMaxPermissions = fn } // FindSession finds a session based on the session key func (s *Service) FindSession(ctx context.Context, key string) (*influxdb.Session, error) { session, err := s.store.FindSessionByKey(ctx, key) if err != nil { return nil, err } // TODO: We want to be able to store permissions in the session // but the contract provided by urm's doesn't give us enough information to quickly repopulate our // session permissions on updates so we are required to pull the permissions every time we find the session. permissions, err := s.getPermissionSet(ctx, session.UserID) if err != nil { return nil, err } session.Permissions = permissions return session, nil } // ExpireSession removes a session from the system func (s *Service) ExpireSession(ctx context.Context, key string) error { session, err := s.store.FindSessionByKey(ctx, key) if err != nil { return err } return s.store.DeleteSession(ctx, session.ID) } // CreateSession func (s *Service) CreateSession(ctx context.Context, user string) (*influxdb.Session, error) { u, err := s.userService.FindUser(ctx, influxdb.UserFilter{ Name: &user, }) if err != nil { return nil, err } token, err := s.tokenGen.Token() if err != nil { return nil, err } // for now we are not storing the permissions because we need to pull them every time we find // so we might as well keep the session stored small now := time.Now() session := &influxdb.Session{ ID: s.idGen.ID(), Key: token, CreatedAt: now, ExpiresAt: now.Add(s.sessionLength), UserID: u.ID, } return session, s.store.CreateSession(ctx, session) } // RenewSession update the sessions expiration time func (s *Service) RenewSession(ctx context.Context, session *influxdb.Session, newExpiration time.Time) error { if session == nil { return &errors.Error{ Msg: "session is nil", } } return s.store.RefreshSession(ctx, session.ID, newExpiration) } func (s *Service) getPermissionSet(ctx context.Context, uid platform.ID) ([]influxdb.Permission, error) { mappings, _, err := s.urmService.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{UserID: uid}, influxdb.FindOptions{Limit: 100}) if err != nil { return nil, err } permissions, err := permissionFromMapping(mappings) if err != nil { return nil, err } if len(mappings) == 100 { // if we got 100 mappings we probably need to pull more pages // account for paginated results for i := len(mappings); len(mappings) > 0; i += len(mappings) { mappings, _, err = s.urmService.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{UserID: uid}, influxdb.FindOptions{Offset: i, Limit: 100}) if err != nil { return nil, err } pms, err := permissionFromMapping(mappings) if err != nil { return nil, err } permissions = append(permissions, pms...) } } if !s.disableAuthorizationsForMaxPermissions(ctx) { as, _, err := s.authService.FindAuthorizations(ctx, influxdb.AuthorizationFilter{UserID: &uid}) if err != nil { return nil, err } for _, a := range as { permissions = append(permissions, a.Permissions...) } } permissions = append(permissions, influxdb.MePermissions(uid)...) return permissions, nil } func permissionFromMapping(mappings []*influxdb.UserResourceMapping) ([]influxdb.Permission, error) { ps := make([]influxdb.Permission, 0, len(mappings)) for _, m := range mappings { p, err := m.ToPermissions() if err != nil { return nil, &errors.Error{ Err: err, } } ps = append(ps, p...) } return ps, nil }