...

Source file src/edge-infra.dev/pkg/edge/iam/storage/database/client_storage.go

Documentation: edge-infra.dev/pkg/edge/iam/storage/database

     1  package database
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/ory/fosite"
    11  	"github.com/pkg/errors"
    12  
    13  	"edge-infra.dev/pkg/edge/iam/client"
    14  )
    15  
    16  // ClientProfileDB is the client profile that is persisted in redis
    17  type ClientProfileDB struct {
    18  	ClientName        string   `json:"clientName"`
    19  	ID                string   `json:"id"`
    20  	Name              string   `json:"name"`
    21  	Owner             string   `json:"owner"`
    22  	RedirectURIs      []string `json:"redirect_uris"`
    23  	PrintBarcodeURI   string   `json:"print_barcode_uri"`
    24  	PrintBarcodeTypes []string `json:"print_barcode_types"`
    25  	GrantTypes        []string `json:"grant_types"`
    26  	ResponseTypes     []string `json:"response_types"`
    27  	ResponseModes     []string `json:"response_modes"`
    28  	Scopes            []string `json:"scopes"`
    29  	Audience          []string `json:"audience"`
    30  	Roles             []string `json:"roles"`
    31  	Public            bool     `json:"public"`
    32  }
    33  
    34  // ClientCredentialsDB is the client credentials that is persisted in redis
    35  type ClientCredentialsDB struct {
    36  	Secret string `json:"client_secret"`
    37  }
    38  
    39  // GetClient loads the client by its ID or returns an error
    40  // if the client does not exist or another error occurred.
    41  func (s *Store) GetClient(ctx context.Context, id string) (fosite.Client, error) {
    42  	return s.GetIAMClient(ctx, id)
    43  }
    44  
    45  // ClientAssertionJWTValid returns an error if the JTI is
    46  // known or the DB check failed and nil if the JTI is not known.
    47  func (s *Store) ClientAssertionJWTValid(_ context.Context, _ string) error {
    48  	return nil
    49  }
    50  
    51  // SetClientAssertionJWT marks a JTI as known for the given
    52  // expiry time. Before inserting the new JTI, it will clean
    53  // up any existing JTIs that have expired as those tokens can
    54  // not be replayed due to the expiry.
    55  func (s *Store) SetClientAssertionJWT(_ context.Context, _ string, _ time.Time) error {
    56  	return nil
    57  }
    58  
    59  // GetClient returns an EdgeIAMClient by client id
    60  func (s *Store) GetIAMClient(ctx context.Context, id string) (*client.Client, error) {
    61  	profileKey := keyFrom(KeyPrefixClientProfile, id)
    62  	var doc *Doc
    63  	var err error
    64  	if doc, err = s.getDoc(ctx, profileKey); doc == nil || err != nil {
    65  		return nil, errors.New("failed to get client in storage")
    66  	}
    67  	profile := doc.Value
    68  
    69  	credsKey := keyFrom(KeyPrefixClientCreds, id)
    70  	if doc, err = s.getDoc(ctx, credsKey); err != nil {
    71  		return nil, err
    72  	}
    73  	if doc == nil {
    74  		return nil, errors.New("no credentials found for client in storage`")
    75  	}
    76  	creds := doc.Value
    77  
    78  	iamClient, err := toEdgeIAMClient(id, []byte(profile), []byte(creds))
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	return iamClient, nil
    84  }
    85  
    86  func (s *Store) GetClients(ctx context.Context, owner string) ([]*client.Client, error) {
    87  	query := map[string]interface{}{
    88  		"selector": map[string]interface{}{
    89  			"value": map[string]interface{}{
    90  				"owner": owner,
    91  			},
    92  		},
    93  	}
    94  	docs, err := s.findDoc(ctx, query)
    95  	if err != nil {
    96  		s.Log.Info("failed to find clients from db", "owner", owner)
    97  		return nil, err
    98  	}
    99  	// get all client id's based on the redis keys
   100  	ids := make([]string, 0)
   101  	for _, doc := range docs {
   102  		currentKey := doc.ID
   103  		res := strings.Split(currentKey, "client-profile:")
   104  		ids = append(ids, res[1])
   105  	}
   106  
   107  	// iterate the client id's and create an EdgeIAMClient for each from the client profile and credentials values in redis
   108  	iamClients := make([]*client.Client, 0)
   109  	for _, id := range ids {
   110  		c, err := s.GetIAMClient(ctx, id)
   111  		if err != nil {
   112  			s.Log.Info("failed to retrieve client from db", "id", id)
   113  			continue
   114  		}
   115  
   116  		if c != nil {
   117  			iamClients = append(iamClients, c)
   118  		}
   119  	}
   120  
   121  	return iamClients, nil
   122  }
   123  
   124  func (s *Store) SaveClientProfile(ctx context.Context, clientID string, profile *client.Profile) (*client.Profile, error) {
   125  	p := toClientProfileDB(profile)
   126  	p.ID = clientID
   127  
   128  	profileKey := keyFrom(KeyPrefixClientProfile, clientID)
   129  	profileJSON, err := json.Marshal(p)
   130  	if err != nil {
   131  		return nil, errors.WithStack(err)
   132  	}
   133  
   134  	if err := s.updateDoc(ctx, profileKey, profileJSON); err != nil {
   135  		return nil, errors.Wrap(err, "failed to save client profile")
   136  	}
   137  
   138  	return profile, nil
   139  }
   140  
   141  func (s *Store) SaveClientCredentials(ctx context.Context, clientID string, credentials *client.Credentials) (*client.Credentials, error) {
   142  	creds := toClientCredentialsDB(credentials)
   143  	credsKey := keyFrom(KeyPrefixClientCreds, clientID)
   144  
   145  	credsJSON, err := json.Marshal(creds)
   146  	if err != nil {
   147  		return nil, errors.WithStack(err)
   148  	}
   149  	if err := s.updateDoc(ctx, credsKey, credsJSON); err != nil {
   150  		return nil, errors.Wrap(err, "failed to save client credentials")
   151  	}
   152  
   153  	return credentials, nil
   154  }
   155  
   156  func (s *Store) DeleteClient(ctx context.Context, id string) error {
   157  	credsKey := keyFrom(KeyPrefixClientCreds, id)
   158  	if err := s.deleteDoc(ctx, credsKey); err != nil {
   159  		return errors.Wrap(err, fmt.Sprintf("failed to delete key '%v'", credsKey))
   160  	}
   161  
   162  	profileKey := keyFrom(KeyPrefixClientProfile, id)
   163  	if err := s.deleteDoc(ctx, profileKey); err != nil {
   164  		return errors.Wrap(err, fmt.Sprintf("failed to delete key '%v'", profileKey))
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  func toClientProfile(profileDB []byte) (*client.Profile, error) {
   171  	var profile ClientProfileDB
   172  	if err := json.Unmarshal(profileDB, &profile); err != nil {
   173  		return nil, errors.WithStack(err)
   174  	}
   175  
   176  	return &client.Profile{
   177  		Name:              profile.Name,
   178  		ClientName:        profile.ClientName,
   179  		Owner:             profile.Owner,
   180  		RedirectURIs:      profile.RedirectURIs,
   181  		ResponseModes:     profile.ResponseModes,
   182  		PrintBarcodeURI:   profile.PrintBarcodeURI,
   183  		PrintBarcodeTypes: profile.PrintBarcodeTypes,
   184  		Scopes:            profile.Scopes,        //[]string{"fosite", "openid", "profile", "offline", "offline_access"},
   185  		GrantTypes:        profile.GrantTypes,    //[]string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"},
   186  		ResponseTypes:     profile.ResponseTypes, //[]string{"code"},
   187  		Roles:             profile.Roles,
   188  		Public:            profile.Public,
   189  	}, nil
   190  }
   191  
   192  func toClientProfileDB(profile *client.Profile) *ClientProfileDB {
   193  	return &ClientProfileDB{
   194  		ClientName:        profile.ClientName,
   195  		Name:              profile.Name,
   196  		Owner:             profile.Owner,
   197  		RedirectURIs:      profile.RedirectURIs,
   198  		GrantTypes:        profile.GrantTypes,
   199  		PrintBarcodeURI:   profile.PrintBarcodeURI,
   200  		ResponseTypes:     profile.ResponseTypes,
   201  		PrintBarcodeTypes: profile.PrintBarcodeTypes,
   202  		ResponseModes:     profile.ResponseModes,
   203  		Scopes:            profile.Scopes,
   204  		Audience:          profile.Audience,
   205  		Roles:             profile.Roles,
   206  		Public:            profile.Public,
   207  	}
   208  }
   209  
   210  func toClientCredentials(credentialsDB []byte) (*client.Credentials, error) {
   211  	var creds ClientCredentialsDB
   212  	if err := json.Unmarshal(credentialsDB, &creds); err != nil {
   213  		return nil, errors.WithStack(err)
   214  	}
   215  
   216  	return &client.Credentials{
   217  		Secret: creds.Secret,
   218  	}, nil
   219  }
   220  
   221  func toClientCredentialsDB(creds *client.Credentials) *ClientCredentialsDB {
   222  	return &ClientCredentialsDB{
   223  		Secret: creds.Secret,
   224  	}
   225  }
   226  
   227  func toEdgeIAMClient(id string, profileDB []byte, credentialsDB []byte) (*client.Client, error) {
   228  	profile, err := toClientProfile(profileDB)
   229  	if err != nil {
   230  		return nil, errors.WithStack(err)
   231  	}
   232  
   233  	credentials, err := toClientCredentials(credentialsDB)
   234  	if err != nil {
   235  		return nil, errors.WithStack(err)
   236  	}
   237  
   238  	return &client.Client{
   239  		ID:          id,
   240  		Profile:     profile,
   241  		Credentials: credentials,
   242  	}, nil
   243  }
   244  

View as plain text