/
user.go
165 lines (140 loc) · 4.62 KB
/
user.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
package api
import (
"context"
"fmt"
"github.com/Optum/dce/pkg/errors"
"github.com/awslabs/aws-lambda-go-api-proxy/gorillamux"
"log"
"net/http"
"strings"
"github.com/Optum/dce/pkg/awsiface"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
)
type dceCtxKeyType string
// DceCtxKey - Context Key
const DceCtxKey dceCtxKeyType = "dce"
// UserGroupName - Has the string to define Users
const UserGroupName = "User"
// AdminGroupName - Has a string to define Admins
const AdminGroupName = "Admin"
// User - Has the username and their role
type User struct {
Username string
Role string
}
// Authorize returns an error if the user is not authorized to act on the principalID
func (u *User) Authorize(principalID string) error {
var err error
if u.Role != AdminGroupName && principalID != u.Username {
err = errors.NewUnathorizedError(fmt.Sprintf("User [%s] with role: [%s] attempted to act on a lease for [%s], but was not authorized",
u.Username, u.Role, principalID))
}
return err
}
// UserDetailer - used for mocking tests
//
//go:generate mockery -name UserDetailer
type UserDetailer interface {
GetUser(reqCtx *events.APIGatewayProxyRequestContext) *User
}
// UserDetails - Gets User information
type UserDetails struct {
CognitoUserPoolID string `env:"COGNITO_USER_POOL_ID" defaultEnv:"DefaultCognitoUserPoolId"`
RolesAttributesAdminName string `env:"COGNITO_ROLES_ATTRIBUTE_ADMIN_NAME" defaultEnv:"DefaultCognitoAdminName"`
CognitoClient awsiface.CognitoIdentityProviderAPI
}
// GetUser - Gets the username and role out of an http request object
// Assumes that the request is via a Lambda event.
// Uses cognito metadata from the request to determine the user info.
// If the request is not authenticated with cognito,
// returns a generic admin user: User{ Username: "", Role: "Admin" }
func (u *UserDetails) GetUser(reqCtx *events.APIGatewayProxyRequestContext) *User {
if reqCtx.Identity.CognitoIdentityPoolID == "" {
// No cognito authentication means the user is considered an admin
return &User{
Role: AdminGroupName,
}
}
congitoSubID := strings.Split(reqCtx.Identity.CognitoAuthenticationProvider, ":CognitoSignIn:")[1]
filter := fmt.Sprintf("sub = \"%s\"", congitoSubID)
users, err := u.CognitoClient.ListUsers(&cognitoidentityprovider.ListUsersInput{
Filter: aws.String(filter),
UserPoolId: aws.String(u.CognitoUserPoolID),
})
if err != nil {
log.Printf("Error listing users from Cognito: %s", err)
return &User{}
}
if len(users.Users) != 1 {
log.Printf("Did not get the current user. Found %d instead of 1.", len(users.Users))
return &User{}
}
user := &User{
Role: UserGroupName,
Username: *users.Users[0].Username,
}
for _, attribute := range users.Users[0].Attributes {
if *attribute.Name == "custom:roles" {
if u.isUserInAdminFromList(*attribute.Value) {
user.Role = AdminGroupName
return user
}
}
}
isAdmin, err := u.isUserInAdminGroup(user.Username)
if err != nil {
log.Printf("Got an error when quering groups for user: %s", err)
return user
}
if isAdmin {
user.Role = AdminGroupName
return user
}
return user
}
func (u *UserDetails) isUserInAdminGroup(username string) (bool, error) {
groups, err := u.CognitoClient.AdminListGroupsForUser(&cognitoidentityprovider.AdminListGroupsForUserInput{
Username: aws.String(username),
UserPoolId: aws.String(u.CognitoUserPoolID),
})
if err != nil {
log.Printf("Was not abile to query a users for its groups: %s", err)
return false, fmt.Errorf("Was not abile to query a users for its groups: %s", err)
}
for _, group := range groups.Groups {
if *group.GroupName == AdminGroupName {
return true, nil
}
}
return false, nil
}
func (u *UserDetails) isUserInAdminFromList(groups string) bool {
for _, group := range strings.Split(groups, ",") {
if strings.TrimSpace(group) == u.RolesAttributesAdminName {
return true
}
}
return false
}
type UserDetailsMiddleware struct {
GorillaMuxAdapter *gorillamux.GorillaMuxAdapter
UserDetailer UserDetailer
}
func (u *UserDetailsMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqCtx, err := u.GorillaMuxAdapter.GetAPIGatewayContext(r)
if err != nil {
log.Printf("Failed to parse context object from request: %s", err)
WriteAPIErrorResponse(w,
errors.NewInternalServer("Internal server error", err),
)
return
}
user := u.UserDetailer.GetUser(&reqCtx)
ctx := context.WithValue(r.Context(), User{}, user)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}