auth

Paddy 2014-11-11 Parent:b3cd7765a7c8 Child:d43c3fbf00f3

69:42bc3e44f4fe Go to Latest

auth/context.go

Stub out sessions. Stop using the Login type when getting profile by Login, removing Logins, or recording Login use. The Login value has to be unique, anyways, and we don't actually know the Login type when getting a profile by Login. That's sort of the point. Create the concept of Sessions and a sessionStore type to manage our authentication sessions with the server. As per OWASP, we're basically just going to use a transparent, SHA256-generated random string as an ID, and store it client-side and server-side and just pass it back and forth. Add the ProfileID to the Grant type, because we need to remember who granted access. That's sort of important. Set a defaultGrantExpiration constant to an hour, so we have that one constant when creating new Grants. Create a helper that pulls the session ID out of an auth cookie, checks it against the sessionStore, and returns the Session if it's valid. Create a helper that pulls the username and password out of a basic auth header. Create a helper that authenticates a user's login and passphrase, checking them against the profileStore securely. Stub out how the cookie checking is going to work for getting grant approval. Fix the stored Grant RedirectURI to be the passed in redirect URI, not the RedirectURI that we ultimately redirect to. This is in accordance with the spec. Store the profile ID from our session in the created Grant. Stub out a GetTokenHandler that will allow users to exchange a Grant for a Token. Set a constant for the current passphrase scheme, which we will increment for each revision to the passphrase scheme, for backwards compatibility. Change the Profile iterations property to an int, not an int64, to match the code.secondbit.org/pass library (which is matching the PBKDF2 library).

History
paddy@50 1 package auth
paddy@50 2
paddy@50 3 import (
paddy@50 4 "html/template"
paddy@50 5 "io"
paddy@55 6 "log"
paddy@50 7 "time"
paddy@50 8
paddy@50 9 "code.secondbit.org/uuid"
paddy@50 10 )
paddy@50 11
paddy@57 12 // Context wraps the different storage interfaces and should
paddy@57 13 // be used as the main point of interaction for the data storage
paddy@57 14 // layer.
paddy@50 15 type Context struct {
paddy@50 16 template *template.Template
paddy@57 17 clients clientStore
paddy@57 18 grants grantStore
paddy@57 19 profiles profileStore
paddy@57 20 tokens tokenStore
paddy@69 21 sessions sessionStore
paddy@50 22 }
paddy@50 23
paddy@57 24 // Render uses the HTML templates associated with the Context to render the
paddy@57 25 // template specified by name to out using data to fill any template variables.
paddy@55 26 func (c Context) Render(out io.Writer, name string, data interface{}) {
paddy@50 27 if c.template == nil {
paddy@57 28 log.Println("No template set on Context, can't render anything!")
paddy@57 29 return
paddy@50 30 }
paddy@55 31 err := c.template.ExecuteTemplate(out, name, data)
paddy@55 32 if err != nil {
paddy@55 33 log.Println("Error executing template", name, ":", err)
paddy@55 34 }
paddy@50 35 }
paddy@50 36
paddy@57 37 // GetClient returns a single Client by its ID from the
paddy@57 38 // clientStore associated with the Context.
paddy@50 39 func (c Context) GetClient(id uuid.ID) (Client, error) {
paddy@50 40 if c.clients == nil {
paddy@50 41 return Client{}, ErrNoClientStore
paddy@50 42 }
paddy@57 43 return c.clients.getClient(id)
paddy@50 44 }
paddy@50 45
paddy@57 46 // SaveClient stores the passed Client in the clientStore
paddy@57 47 // associated with the Context.
paddy@50 48 func (c Context) SaveClient(client Client) error {
paddy@50 49 if c.clients == nil {
paddy@50 50 return ErrNoClientStore
paddy@50 51 }
paddy@57 52 return c.clients.saveClient(client)
paddy@50 53 }
paddy@50 54
paddy@57 55 // UpdateClient applies the specified ClientChange to the Client
paddy@57 56 // with the specified ID in the clientStore associated with the
paddy@57 57 // Context.
paddy@50 58 func (c Context) UpdateClient(id uuid.ID, change ClientChange) error {
paddy@50 59 if c.clients == nil {
paddy@50 60 return ErrNoClientStore
paddy@50 61 }
paddy@57 62 return c.clients.updateClient(id, change)
paddy@50 63 }
paddy@50 64
paddy@57 65 // DeleteClient removes the client with the specified ID from the
paddy@57 66 // clientStore associated with the Context.
paddy@50 67 func (c Context) DeleteClient(id uuid.ID) error {
paddy@50 68 if c.clients == nil {
paddy@50 69 return ErrNoClientStore
paddy@50 70 }
paddy@57 71 return c.clients.deleteClient(id)
paddy@50 72 }
paddy@50 73
paddy@57 74 // ListClientsByOwner returns a slice of up to num Clients, starting at offset (inclusive)
paddy@57 75 // that have the specified OwnerID in the clientStore associated with the Context.
paddy@50 76 func (c Context) ListClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
paddy@50 77 if c.clients == nil {
paddy@50 78 return []Client{}, ErrNoClientStore
paddy@50 79 }
paddy@57 80 return c.clients.listClientsByOwner(ownerID, num, offset)
paddy@50 81 }
paddy@50 82
paddy@57 83 // AddEndpoint stores the specified Endpoint in the clientStore associated with the Context,
paddy@57 84 // and associates the newly-stored Endpoint with the Client specified by the passed ID.
paddy@50 85 func (c Context) AddEndpoint(client uuid.ID, endpoint Endpoint) error {
paddy@50 86 if c.clients == nil {
paddy@50 87 return ErrNoClientStore
paddy@50 88 }
paddy@57 89 return c.clients.addEndpoint(client, endpoint)
paddy@50 90 }
paddy@50 91
paddy@57 92 // RemoveEndpoint deletes the Endpoint with the specified ID from the clientStore associated
paddy@57 93 // with the Context, and disassociates the Endpoint from the specified Client.
paddy@50 94 func (c Context) RemoveEndpoint(client, endpoint uuid.ID) error {
paddy@50 95 if c.clients == nil {
paddy@50 96 return ErrNoClientStore
paddy@50 97 }
paddy@57 98 return c.clients.removeEndpoint(client, endpoint)
paddy@50 99 }
paddy@50 100
paddy@57 101 // CheckEndpoint finds Endpoints in the clientStore associated with the Context that belong
paddy@58 102 // to the Client specified by the passed ID and match the URI passed. URI matches must be
paddy@58 103 // performed according to RFC 3986 Section 6.
paddy@58 104 func (c Context) CheckEndpoint(client uuid.ID, URI string) (bool, error) {
paddy@50 105 if c.clients == nil {
paddy@50 106 return false, ErrNoClientStore
paddy@50 107 }
paddy@58 108 return c.clients.checkEndpoint(client, URI)
paddy@50 109 }
paddy@50 110
paddy@57 111 // ListEndpoints finds Endpoints in the clientStore associated with the Context that belong
paddy@57 112 // to the Client specified by the passed ID. It returns up to num endpoints, starting at offset,
paddy@57 113 // exclusive.
paddy@50 114 func (c Context) ListEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
paddy@50 115 if c.clients == nil {
paddy@50 116 return []Endpoint{}, ErrNoClientStore
paddy@50 117 }
paddy@57 118 return c.clients.listEndpoints(client, num, offset)
paddy@50 119 }
paddy@50 120
paddy@57 121 // CountEndpoints returns the number of Endpoints the are associated with the Client specified by the
paddy@57 122 // passed ID in the clientStore associated with the Context.
paddy@55 123 func (c Context) CountEndpoints(client uuid.ID) (int64, error) {
paddy@55 124 if c.clients == nil {
paddy@55 125 return 0, ErrNoClientStore
paddy@55 126 }
paddy@57 127 return c.clients.countEndpoints(client)
paddy@55 128 }
paddy@55 129
paddy@57 130 // GetGrant returns the Grant specified by the provided code from the grantStore associated with the
paddy@57 131 // Context.
paddy@50 132 func (c Context) GetGrant(code string) (Grant, error) {
paddy@50 133 if c.grants == nil {
paddy@50 134 return Grant{}, ErrNoGrantStore
paddy@50 135 }
paddy@57 136 return c.grants.getGrant(code)
paddy@50 137 }
paddy@50 138
paddy@57 139 // SaveGrant stores the passed Grant in the grantStore associated with the Context.
paddy@50 140 func (c Context) SaveGrant(grant Grant) error {
paddy@50 141 if c.grants == nil {
paddy@50 142 return ErrNoGrantStore
paddy@50 143 }
paddy@57 144 return c.grants.saveGrant(grant)
paddy@50 145 }
paddy@50 146
paddy@57 147 // DeleteGrant removes the Grant specified by the provided code from the grantStore associated with
paddy@57 148 // the Context.
paddy@50 149 func (c Context) DeleteGrant(code string) error {
paddy@50 150 if c.grants == nil {
paddy@50 151 return ErrNoGrantStore
paddy@50 152 }
paddy@57 153 return c.grants.deleteGrant(code)
paddy@50 154 }
paddy@50 155
paddy@57 156 // GetProfileByID returns the Profile specified by the provided ID from the profileStore associated with
paddy@57 157 // the Context.
paddy@50 158 func (c Context) GetProfileByID(id uuid.ID) (Profile, error) {
paddy@50 159 if c.profiles == nil {
paddy@50 160 return Profile{}, ErrNoProfileStore
paddy@50 161 }
paddy@57 162 return c.profiles.getProfileByID(id)
paddy@50 163 }
paddy@50 164
paddy@57 165 // GetProfileByLogin returns the Profile associated with the specified Login from the profileStore associated
paddy@57 166 // with the Context.
paddy@69 167 func (c Context) GetProfileByLogin(value string) (Profile, error) {
paddy@50 168 if c.profiles == nil {
paddy@50 169 return Profile{}, ErrNoProfileStore
paddy@50 170 }
paddy@69 171 return c.profiles.getProfileByLogin(value)
paddy@50 172 }
paddy@50 173
paddy@57 174 // SaveProfile inserts the passed Profile into the profileStore associated with the Context.
paddy@50 175 func (c Context) SaveProfile(profile Profile) error {
paddy@50 176 if c.profiles == nil {
paddy@50 177 return ErrNoProfileStore
paddy@50 178 }
paddy@57 179 return c.profiles.saveProfile(profile)
paddy@50 180 }
paddy@50 181
paddy@57 182 // UpdateProfile applies the supplied ProfileChange to the Profile that matches the specified ID
paddy@57 183 // in the profileStore associated with the Context.
paddy@50 184 func (c Context) UpdateProfile(id uuid.ID, change ProfileChange) error {
paddy@50 185 if c.profiles == nil {
paddy@50 186 return ErrNoProfileStore
paddy@50 187 }
paddy@57 188 return c.profiles.updateProfile(id, change)
paddy@50 189 }
paddy@50 190
paddy@57 191 // UpdateProfiles applies the supplied BulkProfileChange to every Profile that matches one of the
paddy@57 192 // specified IDs in the profileStore associated with the Context.
paddy@50 193 func (c Context) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error {
paddy@50 194 if c.profiles == nil {
paddy@50 195 return ErrNoProfileStore
paddy@50 196 }
paddy@57 197 return c.profiles.updateProfiles(ids, change)
paddy@50 198 }
paddy@50 199
paddy@57 200 // DeleteProfile removes the Profile specified by the passed ID from the profileStore associated
paddy@57 201 // with the Context.
paddy@50 202 func (c Context) DeleteProfile(id uuid.ID) error {
paddy@50 203 if c.profiles == nil {
paddy@50 204 return ErrNoProfileStore
paddy@50 205 }
paddy@57 206 return c.profiles.deleteProfile(id)
paddy@50 207 }
paddy@50 208
paddy@57 209 // AddLogin stores the passed Login in the profileStore associated with the Context. It also associates
paddy@57 210 // the newly-created Login with the Orofile in login.ProfileID.
paddy@50 211 func (c Context) AddLogin(login Login) error {
paddy@50 212 if c.profiles == nil {
paddy@50 213 return ErrNoProfileStore
paddy@50 214 }
paddy@57 215 return c.profiles.addLogin(login)
paddy@50 216 }
paddy@50 217
paddy@57 218 // RemoveLogin removes the specified Login from the profileStore associated with the Context, provided
paddy@57 219 // the Login has a ProfileID property that matches the profile ID passed in. It also disassociates the
paddy@57 220 // deleted Login from the Profile in login.ProfileID.
paddy@69 221 func (c Context) RemoveLogin(value string, profile uuid.ID) error {
paddy@50 222 if c.profiles == nil {
paddy@50 223 return ErrNoProfileStore
paddy@50 224 }
paddy@69 225 return c.profiles.removeLogin(value, profile)
paddy@50 226 }
paddy@50 227
paddy@57 228 // RecordLoginUse sets the LastUsed property of the Login specified in the profileStore associated with
paddy@57 229 // the Context to the value passed in as when.
paddy@69 230 func (c Context) RecordLoginUse(value string, when time.Time) error {
paddy@50 231 if c.profiles == nil {
paddy@50 232 return ErrNoProfileStore
paddy@50 233 }
paddy@69 234 return c.profiles.recordLoginUse(value, when)
paddy@50 235 }
paddy@50 236
paddy@57 237 // ListLogins returns a slice of up to num Logins associated with the specified Profile from the profileStore
paddy@57 238 // associated with the Context, skipping offset Profiles.
paddy@50 239 func (c Context) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) {
paddy@50 240 if c.profiles == nil {
paddy@50 241 return []Login{}, ErrNoProfileStore
paddy@50 242 }
paddy@57 243 return c.profiles.listLogins(profile, num, offset)
paddy@50 244 }
paddy@50 245
paddy@57 246 // GetToken returns the Token specified from the tokenStore associated with the Context.
paddy@57 247 // If refresh is true, the token input should be compared against the refresh tokens, not the
paddy@57 248 // access tokens.
paddy@50 249 func (c Context) GetToken(token string, refresh bool) (Token, error) {
paddy@50 250 if c.tokens == nil {
paddy@50 251 return Token{}, ErrNoTokenStore
paddy@50 252 }
paddy@57 253 return c.tokens.getToken(token, refresh)
paddy@50 254 }
paddy@50 255
paddy@57 256 // SaveToken stores the passed Token in the tokenStore associated with the Context.
paddy@50 257 func (c Context) SaveToken(token Token) error {
paddy@50 258 if c.tokens == nil {
paddy@50 259 return ErrNoTokenStore
paddy@50 260 }
paddy@57 261 return c.tokens.saveToken(token)
paddy@50 262 }
paddy@50 263
paddy@57 264 // RemoveToken removes the Token identified by the passed token string from the tokenStore associated
paddy@57 265 // with the Context.
paddy@50 266 func (c Context) RemoveToken(token string) error {
paddy@50 267 if c.tokens == nil {
paddy@50 268 return ErrNoTokenStore
paddy@50 269 }
paddy@57 270 return c.tokens.removeToken(token)
paddy@50 271 }
paddy@50 272
paddy@57 273 // GetTokensByProfileID returns a slice of up to num Tokens with a ProfileID that matches the specified
paddy@57 274 // profileID from the tokenStore associated with the Context, skipping offset Tokens.
paddy@50 275 func (c Context) GetTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
paddy@50 276 if c.tokens == nil {
paddy@50 277 return []Token{}, ErrNoTokenStore
paddy@50 278 }
paddy@57 279 return c.tokens.getTokensByProfileID(profileID, num, offset)
paddy@50 280 }
paddy@69 281
paddy@69 282 // CreateSession stores the passed Session in the sessionStore associated with the Context.
paddy@69 283 func (c Context) CreateSession(session Session) error {
paddy@69 284 if c.sessions == nil {
paddy@69 285 return ErrNoSessionStore
paddy@69 286 }
paddy@69 287 return c.sessions.createSession(session)
paddy@69 288 }
paddy@69 289
paddy@69 290 // GetSession returns the Session specified from the sessionStore associated with the Context.
paddy@69 291 func (c Context) GetSession(id string) (Session, error) {
paddy@69 292 if c.sessions == nil {
paddy@69 293 return Session{}, ErrNoSessionStore
paddy@69 294 }
paddy@69 295 return c.sessions.getSession(id)
paddy@69 296 }
paddy@69 297
paddy@69 298 // RemoveSession removes the Session identified by the passed ID from the sessionStore associated with
paddy@69 299 // the Context.
paddy@69 300 func (c Context) RemoveSession(id string) error {
paddy@69 301 if c.sessions == nil {
paddy@69 302 return ErrNoSessionStore
paddy@69 303 }
paddy@69 304 return c.sessions.removeSession(id)
paddy@69 305 }
paddy@69 306
paddy@69 307 // ListSessions returns a slice of up to num Sessions from the sessionStore associated with the Context,
paddy@69 308 // ordered by the date they were created, descending. If before.IsZero() returns false, only Sessions
paddy@69 309 // that were created before that time will be returned. If profile is not nil, only Sessions belonging to
paddy@69 310 // that Profile will be returned.
paddy@69 311 func (c Context) ListSessions(profile uuid.ID, before time.Time, num int64) ([]Session, error) {
paddy@69 312 if c.sessions != nil {
paddy@69 313 return []Session{}, ErrNoSessionStore
paddy@69 314 }
paddy@69 315 return c.sessions.listSessions(profile, before, num)
paddy@69 316 }