package database import ( "context" "encoding/json" "fmt" "strings" "time" "github.com/ory/fosite" "github.com/pkg/errors" "edge-infra.dev/pkg/edge/iam/client" ) // ClientProfileDB is the client profile that is persisted in redis type ClientProfileDB struct { ClientName string `json:"clientName"` ID string `json:"id"` Name string `json:"name"` Owner string `json:"owner"` RedirectURIs []string `json:"redirect_uris"` PrintBarcodeURI string `json:"print_barcode_uri"` PrintBarcodeTypes []string `json:"print_barcode_types"` GrantTypes []string `json:"grant_types"` ResponseTypes []string `json:"response_types"` ResponseModes []string `json:"response_modes"` Scopes []string `json:"scopes"` Audience []string `json:"audience"` Roles []string `json:"roles"` Public bool `json:"public"` } // ClientCredentialsDB is the client credentials that is persisted in redis type ClientCredentialsDB struct { Secret string `json:"client_secret"` } // GetClient loads the client by its ID or returns an error // if the client does not exist or another error occurred. func (s *Store) GetClient(ctx context.Context, id string) (fosite.Client, error) { return s.GetIAMClient(ctx, id) } // ClientAssertionJWTValid returns an error if the JTI is // known or the DB check failed and nil if the JTI is not known. func (s *Store) ClientAssertionJWTValid(_ context.Context, _ string) error { return nil } // SetClientAssertionJWT marks a JTI as known for the given // expiry time. Before inserting the new JTI, it will clean // up any existing JTIs that have expired as those tokens can // not be replayed due to the expiry. func (s *Store) SetClientAssertionJWT(_ context.Context, _ string, _ time.Time) error { return nil } // GetClient returns an EdgeIAMClient by client id func (s *Store) GetIAMClient(ctx context.Context, id string) (*client.Client, error) { profileKey := keyFrom(KeyPrefixClientProfile, id) var doc *Doc var err error if doc, err = s.getDoc(ctx, profileKey); doc == nil || err != nil { return nil, errors.New("failed to get client in storage") } profile := doc.Value credsKey := keyFrom(KeyPrefixClientCreds, id) if doc, err = s.getDoc(ctx, credsKey); err != nil { return nil, err } if doc == nil { return nil, errors.New("no credentials found for client in storage`") } creds := doc.Value iamClient, err := toEdgeIAMClient(id, []byte(profile), []byte(creds)) if err != nil { return nil, err } return iamClient, nil } func (s *Store) GetClients(ctx context.Context, owner string) ([]*client.Client, error) { query := map[string]interface{}{ "selector": map[string]interface{}{ "value": map[string]interface{}{ "owner": owner, }, }, } docs, err := s.findDoc(ctx, query) if err != nil { s.Log.Info("failed to find clients from db", "owner", owner) return nil, err } // get all client id's based on the redis keys ids := make([]string, 0) for _, doc := range docs { currentKey := doc.ID res := strings.Split(currentKey, "client-profile:") ids = append(ids, res[1]) } // iterate the client id's and create an EdgeIAMClient for each from the client profile and credentials values in redis iamClients := make([]*client.Client, 0) for _, id := range ids { c, err := s.GetIAMClient(ctx, id) if err != nil { s.Log.Info("failed to retrieve client from db", "id", id) continue } if c != nil { iamClients = append(iamClients, c) } } return iamClients, nil } func (s *Store) SaveClientProfile(ctx context.Context, clientID string, profile *client.Profile) (*client.Profile, error) { p := toClientProfileDB(profile) p.ID = clientID profileKey := keyFrom(KeyPrefixClientProfile, clientID) profileJSON, err := json.Marshal(p) if err != nil { return nil, errors.WithStack(err) } if err := s.updateDoc(ctx, profileKey, profileJSON); err != nil { return nil, errors.Wrap(err, "failed to save client profile") } return profile, nil } func (s *Store) SaveClientCredentials(ctx context.Context, clientID string, credentials *client.Credentials) (*client.Credentials, error) { creds := toClientCredentialsDB(credentials) credsKey := keyFrom(KeyPrefixClientCreds, clientID) credsJSON, err := json.Marshal(creds) if err != nil { return nil, errors.WithStack(err) } if err := s.updateDoc(ctx, credsKey, credsJSON); err != nil { return nil, errors.Wrap(err, "failed to save client credentials") } return credentials, nil } func (s *Store) DeleteClient(ctx context.Context, id string) error { credsKey := keyFrom(KeyPrefixClientCreds, id) if err := s.deleteDoc(ctx, credsKey); err != nil { return errors.Wrap(err, fmt.Sprintf("failed to delete key '%v'", credsKey)) } profileKey := keyFrom(KeyPrefixClientProfile, id) if err := s.deleteDoc(ctx, profileKey); err != nil { return errors.Wrap(err, fmt.Sprintf("failed to delete key '%v'", profileKey)) } return nil } func toClientProfile(profileDB []byte) (*client.Profile, error) { var profile ClientProfileDB if err := json.Unmarshal(profileDB, &profile); err != nil { return nil, errors.WithStack(err) } return &client.Profile{ Name: profile.Name, ClientName: profile.ClientName, Owner: profile.Owner, RedirectURIs: profile.RedirectURIs, ResponseModes: profile.ResponseModes, PrintBarcodeURI: profile.PrintBarcodeURI, PrintBarcodeTypes: profile.PrintBarcodeTypes, Scopes: profile.Scopes, //[]string{"fosite", "openid", "profile", "offline", "offline_access"}, GrantTypes: profile.GrantTypes, //[]string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"}, ResponseTypes: profile.ResponseTypes, //[]string{"code"}, Roles: profile.Roles, Public: profile.Public, }, nil } func toClientProfileDB(profile *client.Profile) *ClientProfileDB { return &ClientProfileDB{ ClientName: profile.ClientName, Name: profile.Name, Owner: profile.Owner, RedirectURIs: profile.RedirectURIs, GrantTypes: profile.GrantTypes, PrintBarcodeURI: profile.PrintBarcodeURI, ResponseTypes: profile.ResponseTypes, PrintBarcodeTypes: profile.PrintBarcodeTypes, ResponseModes: profile.ResponseModes, Scopes: profile.Scopes, Audience: profile.Audience, Roles: profile.Roles, Public: profile.Public, } } func toClientCredentials(credentialsDB []byte) (*client.Credentials, error) { var creds ClientCredentialsDB if err := json.Unmarshal(credentialsDB, &creds); err != nil { return nil, errors.WithStack(err) } return &client.Credentials{ Secret: creds.Secret, }, nil } func toClientCredentialsDB(creds *client.Credentials) *ClientCredentialsDB { return &ClientCredentialsDB{ Secret: creds.Secret, } } func toEdgeIAMClient(id string, profileDB []byte, credentialsDB []byte) (*client.Client, error) { profile, err := toClientProfile(profileDB) if err != nil { return nil, errors.WithStack(err) } credentials, err := toClientCredentials(credentialsDB) if err != nil { return nil, errors.WithStack(err) } return &client.Client{ ID: id, Profile: profile, Credentials: credentials, }, nil }