auth

Paddy 2015-07-18 Parent:0a2c3d677161 Child:b7e685839a1b

180:4b68bac597b7 Go to Latest

auth/context.go

Update client to detect errors. The client doesn't treat non-200 responses as errors automatically, so we need to detect when the response.Errors property is set, and use that to return an error. To avoid the boilerplate and an extensive error system, I just wrapped them in an httpErrors type that implements the error interface. That way the errors can be returned, and callers can type-cast and interrogate them. I also updated the GetLogin function to return an auth.ErrLoginNotFound error when the httpErrors response indicates that's the reason the request failed.

History
1 package auth
3 import (
4 "html/template"
5 "io"
6 "log"
7 "net/url"
8 "time"
10 "code.secondbit.org/events.hg"
11 "code.secondbit.org/uuid.hg"
12 )
14 // Context wraps the different storage interfaces and should
15 // be used as the main point of interaction for the data storage
16 // layer.
17 type Context struct {
18 template *template.Template
19 loginURI *url.URL
20 clients clientStore
21 authCodes authorizationCodeStore
22 profiles profileStore
23 tokens tokenStore
24 sessions sessionStore
25 scopes scopeStore
26 eventsPublisher events.Publisher
27 config Config
28 }
30 // NewContext takes a Config instance and uses it to bootstrap a Context
31 // using the information provided in the Config variable.
32 func NewContext(config Config) (Context, error) {
33 if config.iterations == 0 {
34 return Context{}, ErrConfigNotInitialized
35 }
36 context := Context{
37 clients: config.ClientStore,
38 authCodes: config.AuthCodeStore,
39 profiles: config.ProfileStore,
40 tokens: config.TokenStore,
41 sessions: config.SessionStore,
42 scopes: config.ScopeStore,
43 eventsPublisher: config.EventsPublisher,
44 template: config.Template,
45 config: config,
46 }
47 var err error
48 context.loginURI, err = url.Parse(config.LoginURI)
49 if err != nil {
50 log.Println(err)
51 return Context{}, ErrInvalidLoginURI
52 }
53 return context, nil
54 }
56 // Render uses the HTML templates associated with the Context to render the
57 // template specified by name to out using data to fill any template variables.
58 func (c Context) Render(out io.Writer, name string, data interface{}) {
59 if c.template == nil {
60 log.Println("No template set on Context, can't render anything!")
61 return
62 }
63 err := c.template.ExecuteTemplate(out, name, data)
64 if err != nil {
65 log.Println("Error executing template", name, ":", err)
66 }
67 }
69 // GetClient returns a single Client by its ID from the
70 // clientStore associated with the Context.
71 func (c Context) GetClient(id uuid.ID) (Client, error) {
72 if c.clients == nil {
73 return Client{}, ErrNoClientStore
74 }
75 return c.clients.getClient(id)
76 }
78 // SaveClient stores the passed Client in the clientStore
79 // associated with the Context.
80 func (c Context) SaveClient(client Client) error {
81 if c.clients == nil {
82 return ErrNoClientStore
83 }
84 return c.clients.saveClient(client)
85 }
87 // UpdateClient applies the specified ClientChange to the Client
88 // with the specified ID in the clientStore associated with the
89 // Context.
90 func (c Context) UpdateClient(id uuid.ID, change ClientChange) error {
91 if c.clients == nil {
92 return ErrNoClientStore
93 }
94 return c.clients.updateClient(id, change)
95 }
97 // ListClientsByOwner returns a slice of up to num Clients, starting at offset (inclusive)
98 // that have the specified OwnerID in the clientStore associated with the Context.
99 func (c Context) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
100 if c.clients == nil {
101 return []Client{}, ErrNoClientStore
102 }
103 return c.clients.listClientsByOwner(ownerID, num, offset)
104 }
106 func (c Context) DeleteClientsByOwner(ownerID uuid.ID) error {
107 if c.clients == nil {
108 return ErrNoClientStore
109 }
110 return c.clients.deleteClientsByOwner(ownerID)
111 }
113 // AddEndpoints stores the specified Endpoints in the clientStore associated with the Context.
114 func (c Context) AddEndpoints(endpoints []Endpoint) error {
115 if c.clients == nil {
116 return ErrNoClientStore
117 }
118 for pos, endpoint := range endpoints {
119 u, err := normalizeURIString(endpoint.URI)
120 if err != nil {
121 return err
122 }
123 endpoint.NormalizedURI = u
124 endpoints[pos] = endpoint
125 }
126 return c.clients.addEndpoints(endpoints)
127 }
129 // GetEndpoint retrieves the Endpoint with the specified ID from the clientStore associated
130 // with the Context, if and only if it belongs to the Client with the specified ID.
131 func (c Context) GetEndpoint(client, endpoint uuid.ID) (Endpoint, error) {
132 if c.clients == nil {
133 return Endpoint{}, ErrNoClientStore
134 }
135 return c.clients.getEndpoint(client, endpoint)
136 }
138 // RemoveEndpoint deletes the Endpoint with the specified ID from the clientStore associated
139 // with the Context, and disassociates the Endpoint from the specified Client.
140 func (c Context) RemoveEndpoint(client, endpoint uuid.ID) error {
141 if c.clients == nil {
142 return ErrNoClientStore
143 }
144 return c.clients.removeEndpoint(client, endpoint)
145 }
147 // CheckEndpoint finds Endpoints in the clientStore associated with the Context that belong
148 // to the Client specified by the passed ID and match the URI passed. URI matches must be
149 // performed according to RFC 3986 Section 6.
150 func (c Context) CheckEndpoint(client uuid.ID, URI string) (bool, error) {
151 if c.clients == nil {
152 return false, ErrNoClientStore
153 }
154 u, err := normalizeURIString(URI)
155 if err != nil {
156 return false, err
157 }
158 return c.clients.checkEndpoint(client, u)
159 }
161 // ListEndpoints finds Endpoints in the clientStore associated with the Context that belong
162 // to the Client specified by the passed ID. It returns up to num endpoints, starting at offset,
163 // exclusive.
164 func (c Context) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
165 if c.clients == nil {
166 return []Endpoint{}, ErrNoClientStore
167 }
168 return c.clients.listEndpoints(client, num, offset)
169 }
171 func (c Context) RemoveEndpointsByClientID(client uuid.ID) error {
172 if c.clients == nil {
173 return ErrNoClientStore
174 }
175 return c.clients.removeEndpointsByClientID(client)
176 }
178 // CountEndpoints returns the number of Endpoints the are associated with the Client specified by the
179 // passed ID in the clientStore associated with the Context.
180 func (c Context) CountEndpoints(client uuid.ID) (int64, error) {
181 if c.clients == nil {
182 return 0, ErrNoClientStore
183 }
184 return c.clients.countEndpoints(client)
185 }
187 // GetAuthorizationCode returns the AuthorizationCode specified by the provided code from the authorizationCodeStore associated with the
188 // Context.
189 func (c Context) GetAuthorizationCode(code string) (AuthorizationCode, error) {
190 if c.authCodes == nil {
191 return AuthorizationCode{}, ErrNoAuthorizationCodeStore
192 }
193 return c.authCodes.getAuthorizationCode(code)
194 }
196 // SaveAuthorizationCode stores the passed AuthorizationCode in the authorizationCodeStore associated with the Context.
197 func (c Context) SaveAuthorizationCode(authCode AuthorizationCode) error {
198 if c.authCodes == nil {
199 return ErrNoAuthorizationCodeStore
200 }
201 return c.authCodes.saveAuthorizationCode(authCode)
202 }
204 // DeleteAuthorizationCode removes the AuthorizationCode specified by the provided code from the authorizationCodeStore associated with
205 // the Context.
206 func (c Context) DeleteAuthorizationCode(code string) error {
207 if c.authCodes == nil {
208 return ErrNoAuthorizationCodeStore
209 }
210 return c.authCodes.deleteAuthorizationCode(code)
211 }
213 // DeleteAuthorizationCodesByProfileID removes the AuthorizationCodes associated with the Profile specified by the provided ID from the
214 // authorizationCodeStore associated with the Context.
215 func (c Context) DeleteAuthorizationCodesByProfileID(profileID uuid.ID) error {
216 if c.authCodes == nil {
217 return ErrNoAuthorizationCodeStore
218 }
219 return c.authCodes.deleteAuthorizationCodesByProfileID(profileID)
220 }
222 func (c Context) DeleteAuthorizationCodesByClientID(clientID uuid.ID) error {
223 if c.authCodes == nil {
224 return ErrNoAuthorizationCodeStore
225 }
226 return c.authCodes.deleteAuthorizationCodesByClientID(clientID)
227 }
229 // UseAuthorizationCode marks the AuthorizationCode specified by the provided code as used in the authorizationCodeStore associated with
230 // the Context. Once an AuthorizationCode is marked as used, its Used property will be set to true when retrieved from the authorizationCodeStore.
231 func (c Context) UseAuthorizationCode(code string) error {
232 if c.authCodes == nil {
233 return ErrNoAuthorizationCodeStore
234 }
235 return c.authCodes.useAuthorizationCode(code)
236 }
238 // GetProfileByID returns the Profile specified by the provided ID from the profileStore associated with
239 // the Context.
240 func (c Context) GetProfileByID(id uuid.ID) (Profile, error) {
241 if c.profiles == nil {
242 return Profile{}, ErrNoProfileStore
243 }
244 return c.profiles.getProfileByID(id)
245 }
247 // GetProfileByLogin returns the Profile associated with the specified Login from the profileStore associated
248 // with the Context.
249 func (c Context) GetProfileByLogin(value string) (Profile, error) {
250 if c.profiles == nil {
251 return Profile{}, ErrNoProfileStore
252 }
253 return c.profiles.getProfileByLogin(value)
254 }
256 // SaveProfile inserts the passed Profile into the profileStore associated with the Context.
257 func (c Context) SaveProfile(profile Profile) error {
258 if c.profiles == nil {
259 return ErrNoProfileStore
260 }
261 return c.profiles.saveProfile(profile)
262 }
264 // UpdateProfile applies the supplied ProfileChange to the Profile that matches the specified ID
265 // in the profileStore associated with the Context.
266 func (c Context) UpdateProfile(id uuid.ID, change ProfileChange) error {
267 if c.profiles == nil {
268 return ErrNoProfileStore
269 }
270 return c.profiles.updateProfile(id, change)
271 }
273 // UpdateProfiles applies the supplied BulkProfileChange to every Profile that matches one of the
274 // specified IDs in the profileStore associated with the Context.
275 func (c Context) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error {
276 if c.profiles == nil {
277 return ErrNoProfileStore
278 }
279 return c.profiles.updateProfiles(ids, change)
280 }
282 // DeleteProfile removes the specified Profile from the profileStore associated with the Context.
283 func (c Context) DeleteProfile(id uuid.ID) error {
284 if c.profiles == nil {
285 return ErrNoProfileStore
286 }
287 return c.profiles.deleteProfile(id)
288 }
290 // AddLogin stores the passed Login in the profileStore associated with the Context. It also associates
291 // the newly-created Login with the Orofile in login.ProfileID.
292 func (c Context) AddLogin(login Login) error {
293 if c.profiles == nil {
294 return ErrNoProfileStore
295 }
296 return c.profiles.addLogin(login)
297 }
299 // GetLogin returns the Login specified from the profileStore associated with the Context.
300 func (c Context) GetLogin(value string) (Login, error) {
301 if c.profiles == nil {
302 return Login{}, ErrNoProfileStore
303 }
304 return c.profiles.getLogin(value)
305 }
307 // RemoveLogin removes the specified Login from the profileStore associated with the Context, provided
308 // the Login has a ProfileID property that matches the profile ID passed in. It also disassociates the
309 // deleted Login from the Profile in login.ProfileID.
310 func (c Context) RemoveLogin(value string, profile uuid.ID) error {
311 if c.profiles == nil {
312 return ErrNoProfileStore
313 }
314 return c.profiles.removeLogin(value, profile)
315 }
317 // RemoveLoginsByProfile removes all Logins connected to the specified Profile in the profileStore
318 // associated with the Context and disassociates them from the Profile.
319 func (c Context) RemoveLoginsByProfile(profile uuid.ID) error {
320 if c.profiles == nil {
321 return ErrNoProfileStore
322 }
323 return c.profiles.removeLoginsByProfile(profile)
324 }
326 // RecordLoginUse sets the LastUsed property of the Login specified in the profileStore associated with
327 // the Context to the value passed in as when.
328 func (c Context) RecordLoginUse(value string, when time.Time) error {
329 if c.profiles == nil {
330 return ErrNoProfileStore
331 }
332 return c.profiles.recordLoginUse(value, when)
333 }
335 // VerifyLogin sets the Verified property of the Login specied to true in the profileStore associated with
336 // the Context, assuming the Verification property of the Login in the profileStore matches the verification
337 // passed. If the verifications do not match, an ErrLoginVerificationInvalid error is returned.
338 func (c Context) VerifyLogin(value, verification string) error {
339 if c.profiles == nil {
340 return ErrNoProfileStore
341 }
342 return c.profiles.verifyLogin(value, verification)
343 }
345 // ListLogins returns a slice of up to num Logins associated with the specified Profile from the profileStore
346 // associated with the Context, skipping offset Profiles.
347 func (c Context) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) {
348 if c.profiles == nil {
349 return []Login{}, ErrNoProfileStore
350 }
351 return c.profiles.listLogins(profile, num, offset)
352 }
354 // GetToken returns the Token specified from the tokenStore associated with the Context.
355 // If refresh is true, the token input should be compared against the refresh tokens, not the
356 // access tokens.
357 func (c Context) GetToken(token string, refresh bool) (Token, error) {
358 if c.tokens == nil {
359 return Token{}, ErrNoTokenStore
360 }
361 return c.tokens.getToken(token, refresh)
362 }
364 // SaveToken stores the passed Token in the tokenStore associated with the Context.
365 func (c Context) SaveToken(token Token) error {
366 if c.tokens == nil {
367 return ErrNoTokenStore
368 }
369 return c.tokens.saveToken(token)
370 }
372 // RevokeToken revokes the Token identfied by the passed token string from the tokenStore associated
373 // with the context.
374 func (c Context) RevokeToken(token string) error {
375 if c.tokens == nil {
376 return ErrNoTokenStore
377 }
378 return c.tokens.revokeToken(token)
379 }
381 // RevokeTokensByProfileID revokes the Tokens associated with the Profile identified by the passed ID in
382 // the tokenStore associated with the Context.
383 func (c Context) RevokeTokensByProfileID(profileID uuid.ID) error {
384 if c.tokens == nil {
385 return ErrNoTokenStore
386 }
387 return c.tokens.revokeTokensByProfileID(profileID)
388 }
390 func (c Context) RevokeTokensByClientID(clientID uuid.ID) error {
391 if c.tokens == nil {
392 return ErrNoTokenStore
393 }
394 return c.tokens.revokeTokensByClientID(clientID)
395 }
397 // GetTokensByProfileID returns a slice of up to num Tokens with a ProfileID that matches the specified
398 // profileID from the tokenStore associated with the Context, skipping offset Tokens.
399 func (c Context) GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
400 if c.tokens == nil {
401 return []Token{}, ErrNoTokenStore
402 }
403 return c.tokens.getTokensByProfileID(profileID, num, offset)
404 }
406 // CreateSession stores the passed Session in the sessionStore associated with the Context.
407 func (c Context) CreateSession(session Session) error {
408 if c.sessions == nil {
409 return ErrNoSessionStore
410 }
411 return c.sessions.createSession(session)
412 }
414 // GetSession returns the Session specified from the sessionStore associated with the Context.
415 func (c Context) GetSession(id string) (Session, error) {
416 if c.sessions == nil {
417 return Session{}, ErrNoSessionStore
418 }
419 return c.sessions.getSession(id)
420 }
422 // TerminateSession sets the Session identified by the passed ID as inactive in the sessionStore assocated
423 // with the Context.
424 func (c Context) TerminateSession(id string) error {
425 if c.sessions == nil {
426 return ErrNoSessionStore
427 }
428 return c.sessions.terminateSession(id)
429 }
431 // TerminateSessionsByProfile sets the Sessions associated with the passed Profile ID as inactive in the
432 // sessionStore associated with the Context.
433 func (c Context) TerminateSessionsByProfile(profile uuid.ID) error {
434 if c.sessions == nil {
435 return ErrNoSessionStore
436 }
437 return c.sessions.terminateSessionsByProfile(profile)
438 }
440 // RemoveSession removes the Session identified by the passed ID from the sessionStore associated with
441 // the Context.
442 func (c Context) RemoveSession(id string) error {
443 if c.sessions == nil {
444 return ErrNoSessionStore
445 }
446 return c.sessions.removeSession(id)
447 }
449 // ListSessions returns a slice of up to num Sessions from the sessionStore associated with the Context,
450 // ordered by the date they were created, descending. If before.IsZero() returns false, only Sessions
451 // that were created before that time will be returned. If profile is not nil, only Sessions belonging to
452 // that Profile will be returned.
453 func (c Context) ListSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) {
454 if c.sessions == nil {
455 return []Session{}, ErrNoSessionStore
456 }
457 return c.sessions.listSessions(profile, before, num)
458 }
460 func (c Context) CreateScopes(scopes []Scope) error {
461 if c.scopes == nil {
462 return ErrNoScopeStore
463 }
464 return c.scopes.createScopes(scopes)
465 }
467 func (c Context) GetScopes(ids []string) ([]Scope, error) {
468 if c.scopes == nil {
469 return []Scope{}, ErrNoScopeStore
470 }
471 return c.scopes.getScopes(ids)
472 }
474 func (c Context) UpdateScope(id string, change ScopeChange) error {
475 if c.scopes == nil {
476 return ErrNoScopeStore
477 }
478 return c.scopes.updateScope(id, change)
479 }
481 func (c Context) RemoveScopes(ids []string) error {
482 if c.scopes == nil {
483 return ErrNoScopeStore
484 }
485 return c.scopes.removeScopes(ids)
486 }
488 func (c Context) ListScopes() ([]Scope, error) {
489 if c.scopes == nil {
490 return []Scope{}, ErrNoScopeStore
491 }
492 return c.scopes.listScopes()
493 }
495 func (c Context) SendModelEvent(m events.Model, action string) error {
496 if c.eventsPublisher == nil {
497 log.Println("Event publisher not set!")
498 }
499 return events.PublishModelEvent(c.eventsPublisher, m, action)
500 }