Use postgres arrays for scope associations.
Use the new pqarrays library I wrote to store Scope associations for Tokens and
AuthorizationCodes, instead of using our hacky and abstraction-breaking
many-to-many code.
We also created the authStore.deleteAuthorizationCodesByProfileID method, to
clear out the AuthorizationCodes that belong to a Profile (used when the Profile
is deleted). So we added the implementation for memstore and for our postgres
store.
Call Context.DeleteAuthorizationCodesByProfileID when deleting a Profile to
clean up after it.
Rename sortedScopes to Scopes, which we use pqarrays.StringArray's methods on to
fulfill the sql.Scanner and driver.Valuer interfaces. This lets us store Scopes
in postgres arrays.
Create a stringsToScopes helper function that creates Scope objects, with their
IDs filled by the strings specified.
Update our GrantType.Validate function signature to return Scopes instead of
[]string.
Create a Scopes.Strings() helper method that returns a []string of the IDs of
the Scopes.
Update our SQL init file to use the new postgres array definition, instead of
the many-to-many definition.
10 "code.secondbit.org/uuid.hg"
13 // Context wraps the different storage interfaces and should
14 // be used as the main point of interaction for the data storage
17 template *template.Template
20 authCodes authorizationCodeStore
28 // NewContext takes a Config instance and uses it to bootstrap a Context
29 // using the information provided in the Config variable.
30 func NewContext(config Config) (Context, error) {
31 if config.iterations == 0 {
32 return Context{}, ErrConfigNotInitialized
35 clients: config.ClientStore,
36 authCodes: config.AuthCodeStore,
37 profiles: config.ProfileStore,
38 tokens: config.TokenStore,
39 sessions: config.SessionStore,
40 scopes: config.ScopeStore,
41 template: config.Template,
45 context.loginURI, err = url.Parse(config.LoginURI)
48 return Context{}, ErrInvalidLoginURI
53 // Render uses the HTML templates associated with the Context to render the
54 // template specified by name to out using data to fill any template variables.
55 func (c Context) Render(out io.Writer, name string, data interface{}) {
56 if c.template == nil {
57 log.Println("No template set on Context, can't render anything!")
60 err := c.template.ExecuteTemplate(out, name, data)
62 log.Println("Error executing template", name, ":", err)
66 // GetClient returns a single Client by its ID from the
67 // clientStore associated with the Context.
68 func (c Context) GetClient(id uuid.ID) (Client, error) {
70 return Client{}, ErrNoClientStore
72 return c.clients.getClient(id)
75 // SaveClient stores the passed Client in the clientStore
76 // associated with the Context.
77 func (c Context) SaveClient(client Client) error {
79 return ErrNoClientStore
81 return c.clients.saveClient(client)
84 // UpdateClient applies the specified ClientChange to the Client
85 // with the specified ID in the clientStore associated with the
87 func (c Context) UpdateClient(id uuid.ID, change ClientChange) error {
89 return ErrNoClientStore
91 return c.clients.updateClient(id, change)
94 // ListClientsByOwner returns a slice of up to num Clients, starting at offset (inclusive)
95 // that have the specified OwnerID in the clientStore associated with the Context.
96 func (c Context) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
98 return []Client{}, ErrNoClientStore
100 return c.clients.listClientsByOwner(ownerID, num, offset)
103 // AddEndpoints stores the specified Endpoints in the clientStore associated with the Context.
104 func (c Context) AddEndpoints(endpoints []Endpoint) error {
105 if c.clients == nil {
106 return ErrNoClientStore
108 for pos, endpoint := range endpoints {
109 u, err := normalizeURIString(endpoint.URI)
113 endpoint.NormalizedURI = u
114 endpoints[pos] = endpoint
116 return c.clients.addEndpoints(endpoints)
119 // GetEndpoint retrieves the Endpoint with the specified ID from the clientStore associated
120 // with the Context, if and only if it belongs to the Client with the specified ID.
121 func (c Context) GetEndpoint(client, endpoint uuid.ID) (Endpoint, error) {
122 if c.clients == nil {
123 return Endpoint{}, ErrNoClientStore
125 return c.clients.getEndpoint(client, endpoint)
128 // RemoveEndpoint deletes the Endpoint with the specified ID from the clientStore associated
129 // with the Context, and disassociates the Endpoint from the specified Client.
130 func (c Context) RemoveEndpoint(client, endpoint uuid.ID) error {
131 if c.clients == nil {
132 return ErrNoClientStore
134 return c.clients.removeEndpoint(client, endpoint)
137 // CheckEndpoint finds Endpoints in the clientStore associated with the Context that belong
138 // to the Client specified by the passed ID and match the URI passed. URI matches must be
139 // performed according to RFC 3986 Section 6.
140 func (c Context) CheckEndpoint(client uuid.ID, URI string) (bool, error) {
141 if c.clients == nil {
142 return false, ErrNoClientStore
144 u, err := normalizeURIString(URI)
148 return c.clients.checkEndpoint(client, u)
151 // ListEndpoints finds Endpoints in the clientStore associated with the Context that belong
152 // to the Client specified by the passed ID. It returns up to num endpoints, starting at offset,
154 func (c Context) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
155 if c.clients == nil {
156 return []Endpoint{}, ErrNoClientStore
158 return c.clients.listEndpoints(client, num, offset)
161 // CountEndpoints returns the number of Endpoints the are associated with the Client specified by the
162 // passed ID in the clientStore associated with the Context.
163 func (c Context) CountEndpoints(client uuid.ID) (int64, error) {
164 if c.clients == nil {
165 return 0, ErrNoClientStore
167 return c.clients.countEndpoints(client)
170 // GetAuthorizationCode returns the AuthorizationCode specified by the provided code from the authorizationCodeStore associated with the
172 func (c Context) GetAuthorizationCode(code string) (AuthorizationCode, error) {
173 if c.authCodes == nil {
174 return AuthorizationCode{}, ErrNoAuthorizationCodeStore
176 return c.authCodes.getAuthorizationCode(code)
179 // SaveAuthorizationCode stores the passed AuthorizationCode in the authorizationCodeStore associated with the Context.
180 func (c Context) SaveAuthorizationCode(authCode AuthorizationCode) error {
181 if c.authCodes == nil {
182 return ErrNoAuthorizationCodeStore
184 return c.authCodes.saveAuthorizationCode(authCode)
187 // DeleteAuthorizationCode removes the AuthorizationCode specified by the provided code from the authorizationCodeStore associated with
189 func (c Context) DeleteAuthorizationCode(code string) error {
190 if c.authCodes == nil {
191 return ErrNoAuthorizationCodeStore
193 return c.authCodes.deleteAuthorizationCode(code)
196 // DeleteAuthorizationCodesByProfileID removes the AuthorizationCodes associated with the Profile specified by the provided ID from the
197 // authorizationCodeStore associated with the Context.
198 func (c Context) DeleteAuthorizationCodesByProfileID(profileID uuid.ID) error {
199 if c.authCodes == nil {
200 return ErrNoAuthorizationCodeStore
202 return c.authCodes.deleteAuthorizationCodesByProfileID(profileID)
205 // UseAuthorizationCode marks the AuthorizationCode specified by the provided code as used in the authorizationCodeStore associated with
206 // the Context. Once an AuthorizationCode is marked as used, its Used property will be set to true when retrieved from the authorizationCodeStore.
207 func (c Context) UseAuthorizationCode(code string) error {
208 if c.authCodes == nil {
209 return ErrNoAuthorizationCodeStore
211 return c.authCodes.useAuthorizationCode(code)
214 // GetProfileByID returns the Profile specified by the provided ID from the profileStore associated with
216 func (c Context) GetProfileByID(id uuid.ID) (Profile, error) {
217 if c.profiles == nil {
218 return Profile{}, ErrNoProfileStore
220 return c.profiles.getProfileByID(id)
223 // GetProfileByLogin returns the Profile associated with the specified Login from the profileStore associated
225 func (c Context) GetProfileByLogin(value string) (Profile, error) {
226 if c.profiles == nil {
227 return Profile{}, ErrNoProfileStore
229 return c.profiles.getProfileByLogin(value)
232 // SaveProfile inserts the passed Profile into the profileStore associated with the Context.
233 func (c Context) SaveProfile(profile Profile) error {
234 if c.profiles == nil {
235 return ErrNoProfileStore
237 return c.profiles.saveProfile(profile)
240 // UpdateProfile applies the supplied ProfileChange to the Profile that matches the specified ID
241 // in the profileStore associated with the Context.
242 func (c Context) UpdateProfile(id uuid.ID, change ProfileChange) error {
243 if c.profiles == nil {
244 return ErrNoProfileStore
246 return c.profiles.updateProfile(id, change)
249 // UpdateProfiles applies the supplied BulkProfileChange to every Profile that matches one of the
250 // specified IDs in the profileStore associated with the Context.
251 func (c Context) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error {
252 if c.profiles == nil {
253 return ErrNoProfileStore
255 return c.profiles.updateProfiles(ids, change)
258 // DeleteProfile removes the specified Profile from the profileStore associated with the Context.
259 func (c Context) DeleteProfile(id uuid.ID) error {
260 if c.profiles == nil {
261 return ErrNoProfileStore
263 return c.profiles.deleteProfile(id)
266 // AddLogin stores the passed Login in the profileStore associated with the Context. It also associates
267 // the newly-created Login with the Orofile in login.ProfileID.
268 func (c Context) AddLogin(login Login) error {
269 if c.profiles == nil {
270 return ErrNoProfileStore
272 return c.profiles.addLogin(login)
275 // RemoveLogin removes the specified Login from the profileStore associated with the Context, provided
276 // the Login has a ProfileID property that matches the profile ID passed in. It also disassociates the
277 // deleted Login from the Profile in login.ProfileID.
278 func (c Context) RemoveLogin(value string, profile uuid.ID) error {
279 if c.profiles == nil {
280 return ErrNoProfileStore
282 return c.profiles.removeLogin(value, profile)
285 // RemoveLoginsByProfile removes all Logins connected to the specified Profile in the profileStore
286 // associated with the Context and disassociates them from the Profile.
287 func (c Context) RemoveLoginsByProfile(profile uuid.ID) error {
288 if c.profiles == nil {
289 return ErrNoProfileStore
291 return c.profiles.removeLoginsByProfile(profile)
294 // RecordLoginUse sets the LastUsed property of the Login specified in the profileStore associated with
295 // the Context to the value passed in as when.
296 func (c Context) RecordLoginUse(value string, when time.Time) error {
297 if c.profiles == nil {
298 return ErrNoProfileStore
300 return c.profiles.recordLoginUse(value, when)
303 // ListLogins returns a slice of up to num Logins associated with the specified Profile from the profileStore
304 // associated with the Context, skipping offset Profiles.
305 func (c Context) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) {
306 if c.profiles == nil {
307 return []Login{}, ErrNoProfileStore
309 return c.profiles.listLogins(profile, num, offset)
312 // GetToken returns the Token specified from the tokenStore associated with the Context.
313 // If refresh is true, the token input should be compared against the refresh tokens, not the
315 func (c Context) GetToken(token string, refresh bool) (Token, error) {
317 return Token{}, ErrNoTokenStore
319 return c.tokens.getToken(token, refresh)
322 // SaveToken stores the passed Token in the tokenStore associated with the Context.
323 func (c Context) SaveToken(token Token) error {
325 return ErrNoTokenStore
327 return c.tokens.saveToken(token)
330 // RevokeToken revokes the Token identfied by the passed token string from the tokenStore associated
331 // with the context. If refresh is true, the token input should be compared against the refresh tokens,
332 // not the access tokens.
333 func (c Context) RevokeToken(token string, refresh bool) error {
335 return ErrNoTokenStore
337 return c.tokens.revokeToken(token, refresh)
340 // RevokeTokensByProfileID revokes the Tokens associated with the Profile identified by the passed ID in
341 // the tokenStore associated with the Context.
342 func (c Context) RevokeTokensByProfileID(profileID uuid.ID) error {
344 return ErrNoTokenStore
346 return c.tokens.revokeTokensByProfileID(profileID)
349 // GetTokensByProfileID returns a slice of up to num Tokens with a ProfileID that matches the specified
350 // profileID from the tokenStore associated with the Context, skipping offset Tokens.
351 func (c Context) GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
353 return []Token{}, ErrNoTokenStore
355 return c.tokens.getTokensByProfileID(profileID, num, offset)
358 // CreateSession stores the passed Session in the sessionStore associated with the Context.
359 func (c Context) CreateSession(session Session) error {
360 if c.sessions == nil {
361 return ErrNoSessionStore
363 return c.sessions.createSession(session)
366 // GetSession returns the Session specified from the sessionStore associated with the Context.
367 func (c Context) GetSession(id string) (Session, error) {
368 if c.sessions == nil {
369 return Session{}, ErrNoSessionStore
371 return c.sessions.getSession(id)
374 // TerminateSession sets the Session identified by the passed ID as inactive in the sessionStore assocated
376 func (c Context) TerminateSession(id string) error {
377 if c.sessions == nil {
378 return ErrNoSessionStore
380 return c.sessions.terminateSession(id)
383 // TerminateSessionsByProfile sets the Sessions associated with the passed Profile ID as inactive in the
384 // sessionStore associated with the Context.
385 func (c Context) TerminateSessionsByProfile(profile uuid.ID) error {
386 if c.sessions == nil {
387 return ErrNoSessionStore
389 return c.sessions.terminateSessionsByProfile(profile)
392 // RemoveSession removes the Session identified by the passed ID from the sessionStore associated with
394 func (c Context) RemoveSession(id string) error {
395 if c.sessions == nil {
396 return ErrNoSessionStore
398 return c.sessions.removeSession(id)
401 // ListSessions returns a slice of up to num Sessions from the sessionStore associated with the Context,
402 // ordered by the date they were created, descending. If before.IsZero() returns false, only Sessions
403 // that were created before that time will be returned. If profile is not nil, only Sessions belonging to
404 // that Profile will be returned.
405 func (c Context) ListSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) {
406 if c.sessions == nil {
407 return []Session{}, ErrNoSessionStore
409 return c.sessions.listSessions(profile, before, num)
412 func (c Context) CreateScopes(scopes []Scope) error {
414 return ErrNoScopeStore
416 return c.scopes.createScopes(scopes)
419 func (c Context) GetScopes(ids []string) ([]Scope, error) {
421 return []Scope{}, ErrNoScopeStore
423 return c.scopes.getScopes(ids)
426 func (c Context) UpdateScope(id string, change ScopeChange) error {
428 return ErrNoScopeStore
430 return c.scopes.updateScope(id, change)
433 func (c Context) RemoveScopes(ids []string) error {
435 return ErrNoScopeStore
437 return c.scopes.removeScopes(ids)
440 func (c Context) ListScopes() ([]Scope, error) {
442 return []Scope{}, ErrNoScopeStore
444 return c.scopes.listScopes()