diff options
Diffstat (limited to 'app/controllers/api_user.go')
-rw-r--r-- | app/controllers/api_user.go | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/app/controllers/api_user.go b/app/controllers/api_user.go new file mode 100644 index 0000000..df667db --- /dev/null +++ b/app/controllers/api_user.go | |||
@@ -0,0 +1,181 @@ | |||
1 | package controllers | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "net/http" | ||
6 | |||
7 | "code.crute.us/mcrute/cloud-identity-broker/app/models" | ||
8 | |||
9 | glecho "code.crute.us/mcrute/golib/echo" | ||
10 | "code.crute.us/mcrute/golib/echo/controller" | ||
11 | "github.com/labstack/echo/v4" | ||
12 | ) | ||
13 | |||
14 | type APIUserHandler struct { | ||
15 | Store models.UserStore | ||
16 | } | ||
17 | |||
18 | func (h *APIUserHandler) Register(prefix string, r glecho.URLRouter, mw ...echo.MiddlewareFunc) { | ||
19 | // This resource did not exist in the V1 API and thus has no V1 | ||
20 | // representation. We use the default handlers for V1 because otherwise | ||
21 | // requests with V1 Accept headers would result in 406 Unacceptable errors. | ||
22 | gh := &controller.ContentTypeNegotiatingHandler{ | ||
23 | DefaultHandler: h.HandleGet, | ||
24 | Handlers: map[string]echo.HandlerFunc{ | ||
25 | contentTypeV1: h.HandleGet, | ||
26 | contentTypeV2: h.HandleGet, | ||
27 | }, | ||
28 | } | ||
29 | r.GET(prefix, gh.Handle, mw...) | ||
30 | |||
31 | ph := &controller.ContentTypeNegotiatingHandler{ | ||
32 | DefaultHandler: h.HandlePut, | ||
33 | Handlers: map[string]echo.HandlerFunc{ | ||
34 | contentTypeV1: h.HandlePut, | ||
35 | contentTypeV2: h.HandlePut, | ||
36 | }, | ||
37 | } | ||
38 | r.PUT(prefix, ph.Handle, mw...) | ||
39 | |||
40 | poh := &controller.ContentTypeNegotiatingHandler{ | ||
41 | DefaultHandler: h.HandlePost, | ||
42 | Handlers: map[string]echo.HandlerFunc{ | ||
43 | contentTypeV1: h.HandlePost, | ||
44 | contentTypeV2: h.HandlePost, | ||
45 | }, | ||
46 | } | ||
47 | r.POST(prefix, poh.Handle, mw...) | ||
48 | |||
49 | r.DELETE(prefix, h.HandleDelete, mw...) | ||
50 | } | ||
51 | |||
52 | func (h *APIUserHandler) HandleGet(c echo.Context) error { | ||
53 | u, err := h.Store.Get(context.Background(), c.Param("user")) | ||
54 | if err != nil { | ||
55 | return echo.ErrInternalServerError | ||
56 | } | ||
57 | |||
58 | return c.JSON(http.StatusOK, u) | ||
59 | } | ||
60 | |||
61 | func validateKeysAndTokens(in *models.User) error { | ||
62 | for k, v := range in.Keys { | ||
63 | if k != v.KeyId { | ||
64 | return &echo.HTTPError{ | ||
65 | Code: http.StatusBadRequest, | ||
66 | Message: "Key ID must match hash key.", | ||
67 | } | ||
68 | } | ||
69 | |||
70 | if v.PrivateKey == nil && v.PublicKey == nil { | ||
71 | return &echo.HTTPError{ | ||
72 | Code: http.StatusBadRequest, | ||
73 | Message: "One of public_key or private_key must be set", | ||
74 | } | ||
75 | } | ||
76 | |||
77 | if v.PrivateKey != nil && v.PublicKey != nil { | ||
78 | return &echo.HTTPError{ | ||
79 | Code: http.StatusBadRequest, | ||
80 | Message: "Only one of public_key or private_key may be set", | ||
81 | } | ||
82 | } | ||
83 | } | ||
84 | |||
85 | for k, v := range in.AuthTokens { | ||
86 | if k != v.Kind { | ||
87 | return &echo.HTTPError{ | ||
88 | Code: http.StatusBadRequest, | ||
89 | Message: "Token kind must match hash key.", | ||
90 | } | ||
91 | } | ||
92 | } | ||
93 | |||
94 | return nil | ||
95 | } | ||
96 | |||
97 | func (h *APIUserHandler) HandlePut(c echo.Context) error { | ||
98 | var in models.User | ||
99 | if err := c.Echo().JSONSerializer.Deserialize(c, &in); err != nil { | ||
100 | return echo.ErrBadRequest | ||
101 | } | ||
102 | |||
103 | u, err := h.Store.Get(context.Background(), c.Param("user")) | ||
104 | if err != nil { | ||
105 | return echo.ErrInternalServerError | ||
106 | } | ||
107 | |||
108 | if in.Username != u.Username { | ||
109 | return &echo.HTTPError{ | ||
110 | Code: http.StatusBadRequest, | ||
111 | Message: "Username can not be changed. Create a new user.", | ||
112 | } | ||
113 | } | ||
114 | |||
115 | if in.Deleted != nil && u.Deleted == nil { | ||
116 | return &echo.HTTPError{ | ||
117 | Code: http.StatusBadRequest, | ||
118 | Message: "Use the DELETE method to delete a record", | ||
119 | } | ||
120 | } | ||
121 | |||
122 | if err := validateKeysAndTokens(&in); err != nil { | ||
123 | return err | ||
124 | } | ||
125 | |||
126 | err = h.Store.Put(context.Background(), &in) | ||
127 | if err != nil { | ||
128 | return echo.ErrInternalServerError | ||
129 | } | ||
130 | |||
131 | return c.String(http.StatusNoContent, "") | ||
132 | } | ||
133 | |||
134 | func (h *APIUserHandler) HandlePost(c echo.Context) error { | ||
135 | var in models.User | ||
136 | if err := c.Echo().JSONSerializer.Deserialize(c, &in); err != nil { | ||
137 | return echo.ErrBadRequest | ||
138 | } | ||
139 | |||
140 | _, err := h.Store.Get(context.Background(), c.Param("user")) | ||
141 | if err == nil { | ||
142 | return &echo.HTTPError{ | ||
143 | Code: http.StatusConflict, | ||
144 | Message: "User with username already exists.", | ||
145 | } | ||
146 | } | ||
147 | |||
148 | if in.Deleted != nil { | ||
149 | return &echo.HTTPError{ | ||
150 | Code: http.StatusBadRequest, | ||
151 | Message: "Can not create deleted user, set Deleted to null", | ||
152 | } | ||
153 | } | ||
154 | |||
155 | if err := validateKeysAndTokens(&in); err != nil { | ||
156 | return err | ||
157 | } | ||
158 | |||
159 | err = h.Store.Put(context.Background(), &in) | ||
160 | if err != nil { | ||
161 | return echo.ErrInternalServerError | ||
162 | } | ||
163 | |||
164 | c.Response().Header().Add("Location", glecho.URLFor(c, "/api/user", in.Username).String()) | ||
165 | |||
166 | return c.String(http.StatusCreated, "") | ||
167 | } | ||
168 | |||
169 | func (h *APIUserHandler) HandleDelete(c echo.Context) error { | ||
170 | u, err := h.Store.Get(context.Background(), c.Param("user")) | ||
171 | if err != nil { | ||
172 | return echo.ErrInternalServerError | ||
173 | } | ||
174 | |||
175 | err = h.Store.Delete(context.Background(), u) | ||
176 | if err != nil { | ||
177 | return echo.ErrInternalServerError | ||
178 | } | ||
179 | |||
180 | return c.String(http.StatusNoContent, "") | ||
181 | } | ||