-
Notifications
You must be signed in to change notification settings - Fork 70
/
auth.go
394 lines (329 loc) · 13.9 KB
/
auth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
package diygoapi
import (
"context"
"net/http"
"strings"
"time"
"github.com/rs/zerolog"
"golang.org/x/oauth2"
"github.com/gilcrest/diygoapi/errs"
"github.com/gilcrest/diygoapi/secure"
"github.com/gilcrest/diygoapi/uuid"
)
const (
// AppIDHeaderKey is the App ID header key
AppIDHeaderKey string = "X-APP-ID"
// ApiKeyHeaderKey is the API key header key
ApiKeyHeaderKey string = "X-API-KEY"
// AuthProviderHeaderKey is the Authorization provider header key
AuthProviderHeaderKey string = "X-AUTH-PROVIDER"
)
// PermissionServicer allows for creating, updating, reading and deleting a Permission
type PermissionServicer interface {
Create(ctx context.Context, r *CreatePermissionRequest, adt Audit) (*PermissionResponse, error)
FindAll(ctx context.Context) ([]*PermissionResponse, error)
Delete(ctx context.Context, extlID string) (DeleteResponse, error)
}
// RoleServicer allows for creating, updating, reading and deleting a Role
// as well as assigning permissions and users to it.
type RoleServicer interface {
Create(ctx context.Context, r *CreateRoleRequest, adt Audit) (*RoleResponse, error)
}
// AuthenticationServicer represents a service for managing authentication.
//
// For this project, Oauth2 is used for user authentication. It is assumed
// that the actual user interaction is being orchestrated externally and
// the server endpoints are being called after an access token has already
// been retrieved from an authentication provider.
//
// In addition, this project provides for a custom application authentication.
// If an endpoint request is sent using application credentials, then those
// will be used. If none are sent, then the client id from the access token
// must be registered in the system and that is used as the calling application.
// The latter is likely the more common use case.
type AuthenticationServicer interface {
// SelfRegister is used for first-time registration of a Person/User
// in the system (associated with an Organization). This is "self
// registration" as opposed to one person registering another person.
SelfRegister(ctx context.Context, params *AuthenticationParams) (ur *UserResponse, err error)
// FindExistingAuth looks up a User given a Provider and Access Token.
// If a User is not found, an error is returned.
FindExistingAuth(r *http.Request, realm string) (Auth, error)
// FindAppByProviderClientID Finds an App given a Provider Client ID as part
// of an Auth object.
FindAppByProviderClientID(ctx context.Context, realm string, auth Auth) (a *App, err error)
// DetermineAppContext checks to see if the request already has an app as part of
// if it does, use that app as the app for session, if it does not, determine the
// app based on the user's provider client ID. In either case, return a new context
// with an app. If there is no app to be found for either, return an error.
DetermineAppContext(ctx context.Context, auth Auth, realm string) (context.Context, error)
// FindAppByAPIKey finds an app given its External ID and determines
// if the given API key is a valid key for it. It is used as part of
// app authentication.
FindAppByAPIKey(r *http.Request, realm string) (*App, error)
// AuthenticationParamExchange returns a ProviderInfo struct
// after calling remote Oauth2 provider.
AuthenticationParamExchange(ctx context.Context, params *AuthenticationParams) (*ProviderInfo, error)
// NewAuthenticationParams parses the provider and authorization
// headers and returns AuthenticationParams based on the results
NewAuthenticationParams(r *http.Request, realm string) (*AuthenticationParams, error)
}
// AuthorizationServicer represents a service for managing authorization.
type AuthorizationServicer interface {
Authorize(r *http.Request, lgr zerolog.Logger, adt Audit) error
}
// TokenExchanger exchanges an oauth2.Token for a ProviderUserInfo
// struct populated with information retrieved from an authentication provider.
type TokenExchanger interface {
Exchange(ctx context.Context, realm string, provider Provider, token *oauth2.Token) (*ProviderInfo, error)
}
// BearerTokenType is used in authorization to access a resource
const BearerTokenType string = "Bearer"
// Provider defines the provider of authorization (Google, GitHub, Apple, auth0, etc.).
//
// Only Google is used currently.
type Provider uint8
// Provider of authorization
//
// The app uses Oauth2 to authorize users with one of the following Providers
const (
UnknownProvider Provider = iota
Google // Google
)
func (p Provider) String() string {
switch p {
case Google:
return "google"
default:
return "unknown_provider"
}
}
// ParseProvider initializes a Provider given a case-insensitive string
func ParseProvider(s string) Provider {
switch strings.ToLower(s) {
case "google":
return Google
}
return UnknownProvider
}
// ProviderInfo contains information returned from an authorization provider
type ProviderInfo struct {
Provider Provider
TokenInfo *ProviderTokenInfo
UserInfo *ProviderUserInfo
}
// ProviderTokenInfo contains non-user information gleaned from the
// Oauth2 provider's access token and subsequent calls to get information
// about a person using it. See ProviderUserInfo for user information.
type ProviderTokenInfo struct {
// Token is the Oauth2 token. For inbound requests, only the
// Access Token is given in the Authorization header, so the
// other details (Refresh Token, Token Type, Expiry) must be
// retrieved from a 3rd party service. The token's Expiry is
// a calculated time of expiration (estimated). This is a moving
// target as some providers send the actual time of expiration,
// others just send seconds until expiration, which means it's
// a calculation and won't have perfect precision.
Token *oauth2.Token
// Client ID: External ID representing the Oauth2 client which
// authenticated the user.
ClientID string
// Scope: The space separated list of scopes granted to this token.
Scope string
// Audience: Who is the intended audience for this token. In general the
// same as issued_to.
Audience string `json:"audience,omitempty"`
// IssuedTo: To whom was the token issued to. In general the same as
// audience.
IssuedTo string `json:"issued_to,omitempty"`
}
// ProviderUserInfo contains common fields from the various Oauth2 providers.
// Currently only using Google, so looks a lot like Google's.
type ProviderUserInfo struct {
// ID: The obfuscated ID of the user assigned by the authentication provider.
ExternalID string
// Email: The user's email address.
Email string
// VerifiedEmail: Boolean flag which is true if the email address is
// verified. Present only if the email scope is present in the request.
VerifiedEmail bool
// NamePrefix: The name prefix for the Profile (e.g. Mx., Ms., Mr., etc.)
NamePrefix string
// MiddleName: The person's middle name.
MiddleName string
// FirstName: The user's first name.
FirstName string
// FamilyName: The user's last name.
LastName string
// FullName: The user's full name.
FullName string
// NameSuffix: The name suffix for the person's name (e.g. "PhD", "CCNA", "OBE").
// Other examples include generational designations like "Sr." and "Jr." and "I", "II", "III", etc.
NameSuffix string
// Nickname: The person's nickname
Nickname string
// Gender: The user's gender. TODO - setup Gender properly. not binary.
Gender string
// BirthDate: The full birthdate of a person (e.g. Dec 18, 1953)
BirthDate time.Time
// Hd: The hosted domain e.g. example.com if the user is Google apps
// user.
HostedDomain string
// Link: URL of the profile page.
ProfileLink string
// Locale: The user's preferred locale.
Locale string
// Picture: URL of the user's picture image.
Picture string
}
// Auth represents a user's authorization in the database. It captures
// the provider Oauth2 credentials. Users are linked to a Person.
// A single Person could authenticate through multiple providers.
type Auth struct {
// ID is the unique identifier for authorization record in database
ID uuid.UUID
// User is the unique user associated to the authorization record.
//
// A Person can have one or more methods of authentication, however,
// only one per authorization provider is allowed per User.
User *User
// Provider is the authentication provider
Provider Provider
// ProviderClientID is the external ID representing the Oauth2 client which
// authenticated the user.
ProviderClientID string
// ProviderPersonID is the authentication provider's unique person/user ID.
ProviderPersonID string
// Provider Access Token
ProviderAccessToken string
// Provider Access Token Expiration Date/Time
ProviderAccessTokenExpiry time.Time
// Provider Refresh Token
ProviderRefreshToken string
}
// Permission stores an approval of a mode of access to a resource.
type Permission struct {
// ID is the unique ID for the Permission.
ID uuid.UUID
// ExternalID is the unique External ID to be given to outside callers.
ExternalID secure.Identifier
// Resource is a human-readable string which represents a resource (e.g. an HTTP route or document, etc.).
Resource string
// Operation represents the action taken on the resource (e.g. POST, GET, edit, etc.)
Operation string
// Description is what the permission is granting, e.g. "grants ability to edit a billing document".
Description string
// Active is a boolean denoting whether the permission is active (true) or not (false).
Active bool
}
// Validate determines if the Permission is valid
func (p Permission) Validate() error {
const op errs.Op = "diygoapi/Permission.Validate"
switch {
case p.ID == uuid.Nil:
return errs.E(op, errs.Validation, "ID is required")
case p.ExternalID.String() == "":
return errs.E(op, errs.Validation, "External ID is required")
case p.Resource == "":
return errs.E(op, errs.Validation, "Resource is required")
case p.Description == "":
return errs.E(op, errs.Validation, "Description is required")
}
return nil
}
// CreatePermissionRequest is the request struct for creating a permission
type CreatePermissionRequest struct {
// A human-readable string which represents a resource (e.g. an HTTP route or document, etc.).
Resource string `json:"resource"`
// A string representing the action taken on the resource (e.g. POST, GET, edit, etc.)
Operation string `json:"operation"`
// A description of what the permission is granting, e.g. "grants ability to edit a billing document".
Description string `json:"description"`
// A boolean denoting whether the permission is active (true) or not (false).
Active bool `json:"active"`
}
// FindPermissionRequest is the response struct for finding a permission
type FindPermissionRequest struct {
// Unique External ID to be given to outside callers.
ExternalID string `json:"external_id"`
// A human-readable string which represents a resource (e.g. an HTTP route or document, etc.).
Resource string `json:"resource"`
// A string representing the action taken on the resource (e.g. POST, GET, edit, etc.)
Operation string `json:"operation"`
}
// PermissionResponse is the response struct for a permission
type PermissionResponse struct {
// Unique External ID to be given to outside callers.
ExternalID string `json:"external_id"`
// A human-readable string which represents a resource (e.g. an HTTP route or document, etc.).
Resource string `json:"resource"`
// A string representing the action taken on the resource (e.g. POST, GET, edit, etc.)
Operation string `json:"operation"`
// A description of what the permission is granting, e.g. "grants ability to edit a billing document".
Description string `json:"description"`
// A boolean denoting whether the permission is active (true) or not (false).
Active bool `json:"active"`
}
// Role is a job function or title which defines an authority level.
type Role struct {
// The unique ID for the Role.
ID uuid.UUID
// Unique External ID to be given to outside callers.
ExternalID secure.Identifier
// A human-readable code which represents the role.
Code string
// A longer description of the role.
Description string
// A boolean denoting whether the role is active (true) or not (false).
Active bool
// Permissions is the list of permissions allowed for the role.
Permissions []*Permission
}
// Validate determines if the Role is valid.
func (r Role) Validate() error {
const op errs.Op = "diygoapi/Role.Validate"
switch {
case r.ID == uuid.Nil:
return errs.E(op, errs.Validation, "ID is required")
case r.ExternalID.String() == "":
return errs.E(op, errs.Validation, "External ID is required")
case r.Code == "":
return errs.E(op, errs.Validation, "Code is required")
case r.Description == "":
return errs.E(op, errs.Validation, "Description is required")
}
return nil
}
// CreateRoleRequest is the request struct for creating a role
type CreateRoleRequest struct {
// A human-readable code which represents the role.
Code string `json:"role_cd"`
// A longer description of the role.
Description string `json:"role_description"`
// A boolean denoting whether the role is active (true) or not (false).
Active bool `json:"active"`
// The list of permissions to be given to the role
Permissions []*FindPermissionRequest
}
// RoleResponse is the response struct for a Role.
type RoleResponse struct {
// Unique External ID to be given to outside callers.
ExternalID string `json:"external_id"`
// A human-readable code which represents the role.
Code string `json:"role_cd"`
// A longer description of the role.
Description string `json:"role_description"`
// A boolean denoting whether the role is active (true) or not (false).
Active bool `json:"active"`
// Permissions is the list of permissions allowed for the role.
Permissions []*Permission
}
// AuthenticationParams is the parameters needed for authenticating a User.
type AuthenticationParams struct {
// Realm is a description of a protected area, used in the WWW-Authenticate header.
Realm string
// Provider is the authentication provider.
Provider Provider
// Token is the authentication token sent as part of Oauth2.
Token *oauth2.Token
}