1 package storage 2 3 import ( 4 "context" 5 "encoding/json" 6 7 "net/url" 8 "time" 9 10 "github.com/google/uuid" 11 "github.com/ory/fosite" 12 "github.com/pkg/errors" 13 14 "edge-infra.dev/pkg/edge/iam/log" 15 ) 16 17 // Request is a concrete implementation of a fosite.Request, extended to 18 // support the required data for OAuth2 and OpenID. 19 type Request struct { 20 // ID contains the unique request identifier. 21 ID string `bson:"id" json:"id" xml:"id"` 22 // CreateTime is when the resource was created in seconds from the epoch. 23 CreateTime int64 `bson:"createTime" json:"createTime" xml:"createTime"` 24 // UpdateTime is the last time the resource was modified in seconds from 25 // the epoch. 26 UpdateTime int64 `bson:"updateTime" json:"updateTime" xml:"updateTime"` 27 // RequestedAt is the time the request was made. 28 RequestedAt time.Time `bson:"requestedAt" json:"requestedAt" xml:"requestedAt"` 29 // Signature contains a unique session signature. 30 Signature string `bson:"signature" json:"signature" xml:"signature"` 31 // ClientID contains a link to the Client that was used to authenticate 32 // this session. 33 ClientID string `bson:"clientId" json:"clientId" xml:"clientId"` 34 // UserID contains the subject's unique ID which links back to a stored 35 // user account. 36 UserID string `bson:"userId" json:"userId" xml:"userId"` 37 // Scopes contains the scopes that the user requested. 38 RequestedScope fosite.Arguments `bson:"scopes" json:"scopes" xml:"scopes"` 39 // GrantedScope contains the list of scopes that the user was actually 40 // granted. 41 GrantedScope fosite.Arguments `bson:"grantedScopes" json:"grantedScopes" xml:"grantedScopes"` 42 // RequestedAudience contains the audience the user requested. 43 RequestedAudience fosite.Arguments `bson:"requestedAudience" json:"requestedAudience" xml:"requestedAudience"` 44 // GrantedAudience contains the list of audiences the user was actually 45 // granted. 46 GrantedAudience fosite.Arguments `bson:"grantedAudience" json:"grantedAudience" xml:"grantedAudience"` 47 // Form contains the url values that were passed in to authenticate the 48 // user's client session. 49 Form url.Values `bson:"formData" json:"formData" xml:"formData"` 50 // Active is specifically used for Authorize Code flow revocation. 51 Active bool `bson:"active" json:"active" xml:"active"` 52 // Session contains the session data. The underlying structure differs 53 // based on OAuth strategy, so we need to store it as binary-encoded JSON. 54 // Otherwise, it can be stored but not unmarshalled back into a 55 // fosite.Session. 56 //Session []byte `bson:"sessionData" json:"sessionData" xml:"sessionData"` 57 58 Session json.RawMessage `bson:"sessionData" json:"sessionData" xml:"sessionData"` 59 } 60 61 // NewRequest returns a new Redis Store request object. 62 func NewRequest() Request { 63 return Request{ 64 ID: uuid.New().String(), 65 RequestedAt: time.Now(), 66 Signature: "", 67 ClientID: "", 68 UserID: "", 69 RequestedScope: fosite.Arguments{}, 70 GrantedScope: fosite.Arguments{}, 71 Form: make(url.Values), 72 Active: true, 73 Session: nil, 74 } 75 } 76 77 type MaskedRequest struct { 78 // ID contains the unique request identifier. 79 ID string `bson:"id" json:"id" xml:"id"` 80 // CreateTime is when the resource was created in seconds from the epoch. 81 CreateTime int64 `bson:"createTime" json:"createTime" xml:"createTime"` 82 // UpdateTime is the last time the resource was modified in seconds from 83 // the epoch. 84 UpdateTime int64 `bson:"updateTime" json:"updateTime" xml:"updateTime"` 85 // RequestedAt is the time the request was made. 86 RequestedAt time.Time `bson:"requestedAt" json:"requestedAt" xml:"requestedAt"` 87 // Signature contains a unique session signature. 88 Signature string `bson:"signature" json:"signature" xml:"signature"` 89 // ClientID contains a link to the Client that was used to authenticate 90 // this session. 91 ClientID string `bson:"clientId" json:"clientId" xml:"clientId"` 92 // UserID contains the subject's unique ID which links back to a stored 93 // user account. 94 UserID string `bson:"userId" json:"userId" xml:"userId"` 95 // Scopes contains the scopes that the user requested. 96 RequestedScope fosite.Arguments `bson:"scopes" json:"scopes" xml:"scopes"` 97 // GrantedScope contains the list of scopes that the user was actually 98 // granted. 99 GrantedScope fosite.Arguments `bson:"grantedScopes" json:"grantedScopes" xml:"grantedScopes"` 100 // RequestedAudience contains the audience the user requested. 101 RequestedAudience fosite.Arguments `bson:"requestedAudience" json:"requestedAudience" xml:"requestedAudience"` 102 // GrantedAudience contains the list of audiences the user was actually 103 // granted. 104 GrantedAudience fosite.Arguments `bson:"grantedAudience" json:"grantedAudience" xml:"grantedAudience"` 105 // Active is specifically used for Authorize Code flow revocation. 106 Active bool `bson:"active" json:"active" xml:"active"` 107 // Session contains the session data. The underlying structure differs 108 // based on OAuth strategy, so we need to store it as binary-encoded JSON. 109 // Otherwise, it can be stored but not unmarshalled back into a 110 // fosite.Session. 111 //Session []byte `bson:"sessionData" json:"sessionData" xml:"sessionData"` 112 113 Session json.RawMessage `bson:"sessionData" json:"sessionData" xml:"sessionData"` 114 } 115 116 // ToMaskedRequest returns a masked request for logging purposes 117 func (r *Request) ToMaskedRequest(_ context.Context) *MaskedRequest { 118 masked := &MaskedRequest{ 119 ID: r.ID, 120 CreateTime: r.CreateTime, 121 UpdateTime: r.UpdateTime, 122 RequestedAt: r.RequestedAt, 123 ClientID: r.ClientID, 124 UserID: r.UserID, 125 RequestedScope: r.RequestedScope, 126 GrantedScope: r.GrantedScope, 127 RequestedAudience: r.RequestedAudience, 128 GrantedAudience: r.GrantedAudience, 129 Active: r.Active, 130 Session: r.Session, 131 Signature: "*", 132 } 133 134 return masked 135 } 136 137 // ToFositeRequest transforms a Redis request to a fosite.Request 138 func (r *Request) ToFositeRequest(ctx context.Context, session fosite.Session, iamClient fosite.Client) (*fosite.Request, error) { 139 log := log.Get(ctx) 140 if session != nil { 141 if err := json.Unmarshal(r.Session, session); err != nil { 142 return nil, errors.WithStack(err) 143 } 144 } else { 145 log.Info("Got an empty session") 146 } 147 148 req := &fosite.Request{ 149 ID: r.ID, 150 RequestedAt: r.RequestedAt, 151 Client: iamClient, 152 RequestedScope: r.RequestedScope, 153 GrantedScope: r.GrantedScope, 154 Form: r.Form, 155 Session: session, 156 RequestedAudience: r.RequestedAudience, 157 GrantedAudience: r.GrantedAudience, 158 } 159 160 return req, nil 161 } 162 163 // ToStorage transforms a fosite.Request to a storage.Request 164 // Signature is a hash that relates to the underlying request method and may not 165 // be a strict 'signature', for example, authorization code grant passes in an 166 // authorization code. 167 func ToStorage(signature string, r fosite.Requester) Request { 168 session, _ := json.Marshal(r.GetSession()) 169 170 return Request{ 171 ID: r.GetID(), 172 RequestedAt: r.GetRequestedAt(), 173 Signature: signature, 174 ClientID: r.GetClient().GetID(), 175 UserID: r.GetSession().GetSubject(), 176 RequestedScope: r.GetRequestedScopes(), 177 GrantedScope: r.GetGrantedScopes(), 178 RequestedAudience: r.GetRequestedAudience(), 179 GrantedAudience: r.GetGrantedAudience(), 180 Form: r.GetRequestForm(), 181 Active: true, 182 Session: session, 183 } 184 } 185 186 // BarcodeRequest is a request schema to store a barcode session in any of the storage implementations. 187 // type BarcodeRequest struct { 188 // // Subject is the user this barcode is issued to 189 // Subject string `bson:"subject" json:"subject" xml:"subject"` 190 // // Credential is a unique generated credential for a barcode generation request. 191 // Credential string `bson:"credential" json:"credential" xml:"credential"` 192 // // CreatedAt refers to the timestamp when the barcode is generated as per user request. 193 // CreatedAt time.Time `bson:"createdAt" json:"createdAt" xml:"createdAt"` 194 // // ExpiresIn refers to the duration in seconds for which the barcode would be valid after creation. 195 // ExpiresIn int64 `bson:"expiresIn" json:"expiresIn" xml:"expiresIn"` 196 // // Roles refers to authz roles of a User in BSL 197 // Roles string `bson:"rls" json:"rls,omitempty" xml:"rls"` 198 // } 199 200 // type BarcodeCodeRequest struct { 201 // // Subject is the user this barcode is issued to 202 // Subject string `bson:"subject" json:"subject" xml:"subject"` 203 // // ClientID is used to make sure that the same client is the one exchanging the code for the actual barcode 204 // ClientID string `bson:"clientId" json:"clientId" xml:"clientId"` 205 // // CreatedAt refers to the timestamp when the barcode is generated as per user request. 206 // CreatedAt time.Time `bson:"createdAt" json:"createdAt" xml:"createdAt"` 207 // } 208 209 // func (b BarcodeRequest) GetCredential() string { 210 // return b.Credential 211 // } 212 213 // func (b BarcodeRequest) GetCreatedAt() time.Time { 214 // return b.CreatedAt 215 // } 216 217 // func (b BarcodeRequest) GetExpiresIn() int64 { 218 // return b.ExpiresIn 219 // } 220 221 // func (b BarcodeRequest) GetRoles() string { 222 // return b.Roles 223 // } 224 225 // func (b BarcodeRequest) GetSubject() string { 226 // return b.Subject 227 // } 228 229 // func (b BarcodeCodeRequest) GetSubject() string { 230 // return b.Subject 231 // } 232 233 // func (b BarcodeCodeRequest) GetCreatedAt() time.Time { 234 // return b.CreatedAt 235 // } 236 237 // func (b BarcodeCodeRequest) GetClientID() string { 238 // return b.ClientID 239 // } 240 241 // type BarcodeRequester interface { 242 // GetCredential() string 243 // GetCreatedAt() time.Time 244 // GetExpiresIn() int64 245 // GetRoles() string 246 // GetSubject() string 247 // } 248 249 // type BarcodeCodeRequester interface { 250 // GetSubject() string 251 // GetClientID() string 252 // GetCreatedAt() time.Time 253 // } 254