package storage import ( "context" "encoding/json" "net/url" "time" "github.com/google/uuid" "github.com/ory/fosite" "github.com/pkg/errors" "edge-infra.dev/pkg/edge/iam/log" ) // Request is a concrete implementation of a fosite.Request, extended to // support the required data for OAuth2 and OpenID. type Request struct { // ID contains the unique request identifier. ID string `bson:"id" json:"id" xml:"id"` // CreateTime is when the resource was created in seconds from the epoch. CreateTime int64 `bson:"createTime" json:"createTime" xml:"createTime"` // UpdateTime is the last time the resource was modified in seconds from // the epoch. UpdateTime int64 `bson:"updateTime" json:"updateTime" xml:"updateTime"` // RequestedAt is the time the request was made. RequestedAt time.Time `bson:"requestedAt" json:"requestedAt" xml:"requestedAt"` // Signature contains a unique session signature. Signature string `bson:"signature" json:"signature" xml:"signature"` // ClientID contains a link to the Client that was used to authenticate // this session. ClientID string `bson:"clientId" json:"clientId" xml:"clientId"` // UserID contains the subject's unique ID which links back to a stored // user account. UserID string `bson:"userId" json:"userId" xml:"userId"` // Scopes contains the scopes that the user requested. RequestedScope fosite.Arguments `bson:"scopes" json:"scopes" xml:"scopes"` // GrantedScope contains the list of scopes that the user was actually // granted. GrantedScope fosite.Arguments `bson:"grantedScopes" json:"grantedScopes" xml:"grantedScopes"` // RequestedAudience contains the audience the user requested. RequestedAudience fosite.Arguments `bson:"requestedAudience" json:"requestedAudience" xml:"requestedAudience"` // GrantedAudience contains the list of audiences the user was actually // granted. GrantedAudience fosite.Arguments `bson:"grantedAudience" json:"grantedAudience" xml:"grantedAudience"` // Form contains the url values that were passed in to authenticate the // user's client session. Form url.Values `bson:"formData" json:"formData" xml:"formData"` // Active is specifically used for Authorize Code flow revocation. Active bool `bson:"active" json:"active" xml:"active"` // Session contains the session data. The underlying structure differs // based on OAuth strategy, so we need to store it as binary-encoded JSON. // Otherwise, it can be stored but not unmarshalled back into a // fosite.Session. //Session []byte `bson:"sessionData" json:"sessionData" xml:"sessionData"` Session json.RawMessage `bson:"sessionData" json:"sessionData" xml:"sessionData"` } // NewRequest returns a new Redis Store request object. func NewRequest() Request { return Request{ ID: uuid.New().String(), RequestedAt: time.Now(), Signature: "", ClientID: "", UserID: "", RequestedScope: fosite.Arguments{}, GrantedScope: fosite.Arguments{}, Form: make(url.Values), Active: true, Session: nil, } } type MaskedRequest struct { // ID contains the unique request identifier. ID string `bson:"id" json:"id" xml:"id"` // CreateTime is when the resource was created in seconds from the epoch. CreateTime int64 `bson:"createTime" json:"createTime" xml:"createTime"` // UpdateTime is the last time the resource was modified in seconds from // the epoch. UpdateTime int64 `bson:"updateTime" json:"updateTime" xml:"updateTime"` // RequestedAt is the time the request was made. RequestedAt time.Time `bson:"requestedAt" json:"requestedAt" xml:"requestedAt"` // Signature contains a unique session signature. Signature string `bson:"signature" json:"signature" xml:"signature"` // ClientID contains a link to the Client that was used to authenticate // this session. ClientID string `bson:"clientId" json:"clientId" xml:"clientId"` // UserID contains the subject's unique ID which links back to a stored // user account. UserID string `bson:"userId" json:"userId" xml:"userId"` // Scopes contains the scopes that the user requested. RequestedScope fosite.Arguments `bson:"scopes" json:"scopes" xml:"scopes"` // GrantedScope contains the list of scopes that the user was actually // granted. GrantedScope fosite.Arguments `bson:"grantedScopes" json:"grantedScopes" xml:"grantedScopes"` // RequestedAudience contains the audience the user requested. RequestedAudience fosite.Arguments `bson:"requestedAudience" json:"requestedAudience" xml:"requestedAudience"` // GrantedAudience contains the list of audiences the user was actually // granted. GrantedAudience fosite.Arguments `bson:"grantedAudience" json:"grantedAudience" xml:"grantedAudience"` // Active is specifically used for Authorize Code flow revocation. Active bool `bson:"active" json:"active" xml:"active"` // Session contains the session data. The underlying structure differs // based on OAuth strategy, so we need to store it as binary-encoded JSON. // Otherwise, it can be stored but not unmarshalled back into a // fosite.Session. //Session []byte `bson:"sessionData" json:"sessionData" xml:"sessionData"` Session json.RawMessage `bson:"sessionData" json:"sessionData" xml:"sessionData"` } // ToMaskedRequest returns a masked request for logging purposes func (r *Request) ToMaskedRequest(_ context.Context) *MaskedRequest { masked := &MaskedRequest{ ID: r.ID, CreateTime: r.CreateTime, UpdateTime: r.UpdateTime, RequestedAt: r.RequestedAt, ClientID: r.ClientID, UserID: r.UserID, RequestedScope: r.RequestedScope, GrantedScope: r.GrantedScope, RequestedAudience: r.RequestedAudience, GrantedAudience: r.GrantedAudience, Active: r.Active, Session: r.Session, Signature: "*", } return masked } // ToFositeRequest transforms a Redis request to a fosite.Request func (r *Request) ToFositeRequest(ctx context.Context, session fosite.Session, iamClient fosite.Client) (*fosite.Request, error) { log := log.Get(ctx) if session != nil { if err := json.Unmarshal(r.Session, session); err != nil { return nil, errors.WithStack(err) } } else { log.Info("Got an empty session") } req := &fosite.Request{ ID: r.ID, RequestedAt: r.RequestedAt, Client: iamClient, RequestedScope: r.RequestedScope, GrantedScope: r.GrantedScope, Form: r.Form, Session: session, RequestedAudience: r.RequestedAudience, GrantedAudience: r.GrantedAudience, } return req, nil } // ToStorage transforms a fosite.Request to a storage.Request // Signature is a hash that relates to the underlying request method and may not // be a strict 'signature', for example, authorization code grant passes in an // authorization code. func ToStorage(signature string, r fosite.Requester) Request { session, _ := json.Marshal(r.GetSession()) return Request{ ID: r.GetID(), RequestedAt: r.GetRequestedAt(), Signature: signature, ClientID: r.GetClient().GetID(), UserID: r.GetSession().GetSubject(), RequestedScope: r.GetRequestedScopes(), GrantedScope: r.GetGrantedScopes(), RequestedAudience: r.GetRequestedAudience(), GrantedAudience: r.GetGrantedAudience(), Form: r.GetRequestForm(), Active: true, Session: session, } } // BarcodeRequest is a request schema to store a barcode session in any of the storage implementations. // type BarcodeRequest struct { // // Subject is the user this barcode is issued to // Subject string `bson:"subject" json:"subject" xml:"subject"` // // Credential is a unique generated credential for a barcode generation request. // Credential string `bson:"credential" json:"credential" xml:"credential"` // // CreatedAt refers to the timestamp when the barcode is generated as per user request. // CreatedAt time.Time `bson:"createdAt" json:"createdAt" xml:"createdAt"` // // ExpiresIn refers to the duration in seconds for which the barcode would be valid after creation. // ExpiresIn int64 `bson:"expiresIn" json:"expiresIn" xml:"expiresIn"` // // Roles refers to authz roles of a User in BSL // Roles string `bson:"rls" json:"rls,omitempty" xml:"rls"` // } // type BarcodeCodeRequest struct { // // Subject is the user this barcode is issued to // Subject string `bson:"subject" json:"subject" xml:"subject"` // // ClientID is used to make sure that the same client is the one exchanging the code for the actual barcode // ClientID string `bson:"clientId" json:"clientId" xml:"clientId"` // // CreatedAt refers to the timestamp when the barcode is generated as per user request. // CreatedAt time.Time `bson:"createdAt" json:"createdAt" xml:"createdAt"` // } // func (b BarcodeRequest) GetCredential() string { // return b.Credential // } // func (b BarcodeRequest) GetCreatedAt() time.Time { // return b.CreatedAt // } // func (b BarcodeRequest) GetExpiresIn() int64 { // return b.ExpiresIn // } // func (b BarcodeRequest) GetRoles() string { // return b.Roles // } // func (b BarcodeRequest) GetSubject() string { // return b.Subject // } // func (b BarcodeCodeRequest) GetSubject() string { // return b.Subject // } // func (b BarcodeCodeRequest) GetCreatedAt() time.Time { // return b.CreatedAt // } // func (b BarcodeCodeRequest) GetClientID() string { // return b.ClientID // } // type BarcodeRequester interface { // GetCredential() string // GetCreatedAt() time.Time // GetExpiresIn() int64 // GetRoles() string // GetSubject() string // } // type BarcodeCodeRequester interface { // GetSubject() string // GetClientID() string // GetCreatedAt() time.Time // }