auth

Paddy 2015-03-21 Parent:93c758f57c69 Child:8267e1c8bcd1

148:06fb735031bb Go to Latest

auth/client.go

Do a first, naive pass at storing profiles in Postgres. This is untested against an actual database. It's a best-guess attempt at SQL. It _should_ work. I think. Start storing things in Postgres, starting with Profiles and Logins. This necessitates the addition of a Deleted property to the Profile type, because I'm not deleting those in case of accidental deletion. Logins, though, we'll delete. This also necessitates updating the profileStore interface to no longer have a deleteProfile method, because we're tracking that through updates now. Then we need to update our profileStore tests, because they no longer clean up after themselves. Which, come to think of it, may cause some problems later.

History
paddy@6 1 package auth
paddy@0 2
paddy@0 3 import (
paddy@108 4 "crypto/rand"
paddy@108 5 "encoding/hex"
paddy@85 6 "encoding/json"
paddy@31 7 "errors"
paddy@116 8 "log"
paddy@85 9 "net/http"
paddy@41 10 "net/url"
paddy@115 11 "strconv"
paddy@135 12 "strings"
paddy@41 13 "time"
paddy@31 14
paddy@116 15 "github.com/PuerkitoBio/purell"
paddy@116 16 "github.com/gorilla/mux"
paddy@116 17
paddy@107 18 "code.secondbit.org/uuid.hg"
paddy@0 19 )
paddy@0 20
paddy@121 21 func init() {
paddy@121 22 RegisterGrantType("client_credentials", GrantType{
paddy@121 23 Validate: clientCredentialsValidate,
paddy@121 24 Invalidate: nil,
paddy@121 25 IssuesRefresh: true,
paddy@121 26 ReturnToken: RenderJSONToken,
paddy@123 27 AllowsPublic: false,
paddy@124 28 AuditString: clientCredentialsAuditString,
paddy@121 29 })
paddy@121 30 }
paddy@121 31
paddy@31 32 var (
paddy@57 33 // ErrNoClientStore is returned when a Context tries to act on a clientStore without setting one first.
paddy@57 34 ErrNoClientStore = errors.New("no clientStore was specified for the Context")
paddy@57 35 // ErrClientNotFound is returned when a Client is requested but not found in a clientStore.
paddy@57 36 ErrClientNotFound = errors.New("client not found in clientStore")
paddy@57 37 // ErrClientAlreadyExists is returned when a Client is added to a clientStore, but another Client with
paddy@57 38 // the same ID already exists in the clientStore.
paddy@57 39 ErrClientAlreadyExists = errors.New("client already exists in clientStore")
paddy@143 40 // ErrEndpointNotFound is returned when an Endpoint is requested but not found in a clientSTore.
paddy@143 41 ErrEndpointNotFound = errors.New("endpoint not found in clientStore")
paddy@41 42
paddy@57 43 // ErrEmptyChange is returned when a Change has all its properties set to nil.
paddy@57 44 ErrEmptyChange = errors.New("change must have at least one property set")
paddy@57 45 // ErrClientNameTooShort is returned when a Client's Name property is too short.
paddy@57 46 ErrClientNameTooShort = errors.New("client name must be at least 2 characters")
paddy@57 47 // ErrClientNameTooLong is returned when a Client's Name property is too long.
paddy@57 48 ErrClientNameTooLong = errors.New("client name must be at most 32 characters")
paddy@57 49 // ErrClientLogoTooLong is returned when a Client's Logo property is too long.
paddy@57 50 ErrClientLogoTooLong = errors.New("client logo must be at most 1024 characters")
paddy@57 51 // ErrClientLogoNotURL is returned when a Client's Logo property is not a valid absolute URL.
paddy@57 52 ErrClientLogoNotURL = errors.New("client logo must be a valid absolute URL")
paddy@57 53 // ErrClientWebsiteTooLong is returned when a Client's Website property is too long.
paddy@49 54 ErrClientWebsiteTooLong = errors.New("client website must be at most 1024 characters")
paddy@57 55 // ErrClientWebsiteNotURL is returned when a Client's Website property is not a valid absolute URL.
paddy@57 56 ErrClientWebsiteNotURL = errors.New("client website must be a valid absolute URL")
paddy@116 57 // ErrEndpointURINotURL is returned when an Endpoint's URI property is not a valid absolute URL.
paddy@116 58 ErrEndpointURINotURL = errors.New("endpoint URI must be a valid absolute URL")
paddy@31 59 )
paddy@31 60
paddy@115 61 const (
paddy@138 62 clientTypePublic = "public"
paddy@138 63 clientTypeConfidential = "confidential"
paddy@138 64 minClientNameLen = 2
paddy@138 65 maxClientNameLen = 24
paddy@138 66 defaultClientResponseSize = 20
paddy@138 67 maxClientResponseSize = 50
paddy@138 68 defaultEndpointResponseSize = 20
paddy@138 69 maxEndpointResponseSize = 50
paddy@130 70
paddy@130 71 normalizeFlags = purell.FlagsUsuallySafeNonGreedy | purell.FlagSortQuery
paddy@115 72 )
paddy@115 73
paddy@25 74 // Client represents a client that grants access
paddy@25 75 // to the auth server, exchanging grants for tokens,
paddy@25 76 // and tokens for access.
paddy@0 77 type Client struct {
paddy@116 78 ID uuid.ID `json:"id,omitempty"`
paddy@116 79 Secret string `json:"secret,omitempty"`
paddy@116 80 OwnerID uuid.ID `json:"owner_id,omitempty"`
paddy@116 81 Name string `json:"name,omitempty"`
paddy@116 82 Logo string `json:"logo,omitempty"`
paddy@116 83 Website string `json:"website,omitempty"`
paddy@116 84 Type string `json:"type,omitempty"`
paddy@0 85 }
paddy@0 86
paddy@57 87 // ApplyChange applies the properties of the passed
paddy@57 88 // ClientChange to the Client object it is called on.
paddy@39 89 func (c *Client) ApplyChange(change ClientChange) {
paddy@39 90 if change.Secret != nil {
paddy@39 91 c.Secret = *change.Secret
paddy@39 92 }
paddy@39 93 if change.OwnerID != nil {
paddy@39 94 c.OwnerID = change.OwnerID
paddy@39 95 }
paddy@39 96 if change.Name != nil {
paddy@39 97 c.Name = *change.Name
paddy@39 98 }
paddy@39 99 if change.Logo != nil {
paddy@39 100 c.Logo = *change.Logo
paddy@39 101 }
paddy@39 102 if change.Website != nil {
paddy@39 103 c.Website = *change.Website
paddy@39 104 }
paddy@39 105 }
paddy@39 106
paddy@57 107 // ClientChange represents a bundle of options for
paddy@57 108 // updating a Client's mutable data.
paddy@31 109 type ClientChange struct {
paddy@41 110 Secret *string
paddy@41 111 OwnerID uuid.ID
paddy@41 112 Name *string
paddy@41 113 Logo *string
paddy@41 114 Website *string
paddy@31 115 }
paddy@31 116
paddy@57 117 // Validate checks the ClientChange it is called on
paddy@57 118 // and asserts its internal validity, or lack thereof.
paddy@133 119 func (c ClientChange) Validate() []error {
paddy@133 120 errors := []error{}
paddy@42 121 if c.Secret == nil && c.OwnerID == nil && c.Name == nil && c.Logo == nil && c.Website == nil {
paddy@133 122 errors = append(errors, ErrEmptyChange)
paddy@133 123 return errors
paddy@42 124 }
paddy@41 125 if c.Name != nil && len(*c.Name) < 2 {
paddy@133 126 errors = append(errors, ErrClientNameTooShort)
paddy@41 127 }
paddy@41 128 if c.Name != nil && len(*c.Name) > 32 {
paddy@133 129 errors = append(errors, ErrClientNameTooLong)
paddy@41 130 }
paddy@42 131 if c.Logo != nil && *c.Logo != "" {
paddy@42 132 if len(*c.Logo) > 1024 {
paddy@133 133 errors = append(errors, ErrClientLogoTooLong)
paddy@42 134 }
paddy@42 135 u, err := url.Parse(*c.Logo)
paddy@42 136 if err != nil || !u.IsAbs() {
paddy@133 137 errors = append(errors, ErrClientLogoNotURL)
paddy@42 138 }
paddy@41 139 }
paddy@42 140 if c.Website != nil && *c.Website != "" {
paddy@42 141 if len(*c.Website) > 140 {
paddy@133 142 errors = append(errors, ErrClientWebsiteTooLong)
paddy@42 143 }
paddy@42 144 u, err := url.Parse(*c.Website)
paddy@42 145 if err != nil || !u.IsAbs() {
paddy@133 146 errors = append(errors, ErrClientWebsiteNotURL)
paddy@42 147 }
paddy@41 148 }
paddy@133 149 return errors
paddy@39 150 }
paddy@39 151
paddy@123 152 func getClientAuth(w http.ResponseWriter, r *http.Request, allowPublic bool) (uuid.ID, string, bool) {
paddy@85 153 enc := json.NewEncoder(w)
paddy@85 154 clientIDStr, clientSecret, fromAuthHeader := r.BasicAuth()
paddy@85 155 if !fromAuthHeader {
paddy@85 156 clientIDStr = r.PostFormValue("client_id")
paddy@85 157 }
paddy@123 158 if clientIDStr == "" {
paddy@85 159 w.WriteHeader(http.StatusUnauthorized)
paddy@85 160 if fromAuthHeader {
paddy@85 161 w.Header().Set("WWW-Authenticate", "Basic")
paddy@85 162 }
paddy@85 163 renderJSONError(enc, "invalid_client")
paddy@123 164 return nil, "", false
paddy@123 165 }
paddy@129 166 if !allowPublic && !fromAuthHeader {
paddy@129 167 w.WriteHeader(http.StatusBadRequest)
paddy@129 168 renderJSONError(enc, "unauthorized_client")
paddy@129 169 return nil, "", false
paddy@129 170 }
paddy@123 171 clientID, err := uuid.Parse(clientIDStr)
paddy@123 172 if err != nil {
paddy@123 173 log.Println("Error decoding client ID:", err)
paddy@123 174 w.WriteHeader(http.StatusUnauthorized)
paddy@123 175 if fromAuthHeader {
paddy@123 176 w.Header().Set("WWW-Authenticate", "Basic")
paddy@123 177 }
paddy@123 178 renderJSONError(enc, "invalid_client")
paddy@123 179 return nil, "", false
paddy@123 180 }
paddy@123 181 return clientID, clientSecret, true
paddy@123 182 }
paddy@123 183
paddy@123 184 func verifyClient(w http.ResponseWriter, r *http.Request, allowPublic bool, context Context) (uuid.ID, bool) {
paddy@123 185 enc := json.NewEncoder(w)
paddy@123 186 clientID, clientSecret, ok := getClientAuth(w, r, allowPublic)
paddy@123 187 if !ok {
paddy@85 188 return nil, false
paddy@85 189 }
paddy@123 190 _, _, fromAuthHeader := r.BasicAuth()
paddy@85 191 client, err := context.GetClient(clientID)
paddy@85 192 if err == ErrClientNotFound {
paddy@85 193 w.WriteHeader(http.StatusUnauthorized)
paddy@85 194 if fromAuthHeader {
paddy@85 195 w.Header().Set("WWW-Authenticate", "Basic")
paddy@85 196 }
paddy@85 197 renderJSONError(enc, "invalid_client")
paddy@85 198 return nil, false
paddy@85 199 } else if err != nil {
paddy@85 200 w.WriteHeader(http.StatusInternalServerError)
paddy@85 201 renderJSONError(enc, "server_error")
paddy@85 202 return nil, false
paddy@85 203 }
paddy@113 204 if client.Secret != clientSecret { // it's important that any client deemed "public" is not issued a client secret.
paddy@85 205 w.WriteHeader(http.StatusUnauthorized)
paddy@85 206 if fromAuthHeader {
paddy@85 207 w.Header().Set("WWW-Authenticate", "Basic")
paddy@85 208 }
paddy@85 209 renderJSONError(enc, "invalid_client")
paddy@85 210 return nil, false
paddy@85 211 }
paddy@85 212 return clientID, true
paddy@85 213 }
paddy@85 214
paddy@57 215 // Endpoint represents a single URI that a Client
paddy@57 216 // controls. Users will be redirected to these URIs
paddy@57 217 // following successful authorization grants and
paddy@57 218 // exchanges for access tokens.
paddy@41 219 type Endpoint struct {
paddy@116 220 ID uuid.ID `json:"id,omitempty"`
paddy@116 221 ClientID uuid.ID `json:"client_id,omitempty"`
paddy@116 222 URI string `json:"uri,omitempty"`
paddy@116 223 NormalizedURI string `json:"-"`
paddy@116 224 Added time.Time `json:"added,omitempty"`
paddy@116 225 }
paddy@116 226
paddy@116 227 func normalizeURIString(in string) (string, error) {
paddy@130 228 n, err := purell.NormalizeURLString(in, normalizeFlags)
paddy@116 229 if err != nil {
paddy@116 230 log.Println(err)
paddy@116 231 return in, ErrEndpointURINotURL
paddy@116 232 }
paddy@116 233 return n, nil
paddy@116 234 }
paddy@116 235
paddy@116 236 func normalizeURI(in *url.URL) string {
paddy@130 237 return purell.NormalizeURL(in, normalizeFlags)
paddy@41 238 }
paddy@41 239
paddy@41 240 type sortedEndpoints []Endpoint
paddy@41 241
paddy@41 242 func (s sortedEndpoints) Len() int {
paddy@41 243 return len(s)
paddy@41 244 }
paddy@41 245
paddy@41 246 func (s sortedEndpoints) Less(i, j int) bool {
paddy@41 247 return s[i].Added.Before(s[j].Added)
paddy@41 248 }
paddy@41 249
paddy@41 250 func (s sortedEndpoints) Swap(i, j int) {
paddy@41 251 s[i], s[j] = s[j], s[i]
paddy@41 252 }
paddy@41 253
paddy@57 254 type clientStore interface {
paddy@57 255 getClient(id uuid.ID) (Client, error)
paddy@57 256 saveClient(client Client) error
paddy@57 257 updateClient(id uuid.ID, change ClientChange) error
paddy@57 258 deleteClient(id uuid.ID) error
paddy@57 259 listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error)
paddy@41 260
paddy@115 261 addEndpoints(client uuid.ID, endpoint []Endpoint) error
paddy@57 262 removeEndpoint(client, endpoint uuid.ID) error
paddy@143 263 getEndpoint(client, endpoint uuid.ID) (Endpoint, error)
paddy@58 264 checkEndpoint(client uuid.ID, endpoint string) (bool, error)
paddy@57 265 listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error)
paddy@57 266 countEndpoints(client uuid.ID) (int64, error)
paddy@0 267 }
paddy@31 268
paddy@57 269 func (m *memstore) getClient(id uuid.ID) (Client, error) {
paddy@31 270 m.clientLock.RLock()
paddy@31 271 defer m.clientLock.RUnlock()
paddy@31 272 c, ok := m.clients[id.String()]
paddy@31 273 if !ok {
paddy@31 274 return Client{}, ErrClientNotFound
paddy@31 275 }
paddy@31 276 return c, nil
paddy@31 277 }
paddy@31 278
paddy@57 279 func (m *memstore) saveClient(client Client) error {
paddy@31 280 m.clientLock.Lock()
paddy@31 281 defer m.clientLock.Unlock()
paddy@31 282 if _, ok := m.clients[client.ID.String()]; ok {
paddy@31 283 return ErrClientAlreadyExists
paddy@31 284 }
paddy@31 285 m.clients[client.ID.String()] = client
paddy@31 286 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()], client.ID)
paddy@31 287 return nil
paddy@31 288 }
paddy@31 289
paddy@57 290 func (m *memstore) updateClient(id uuid.ID, change ClientChange) error {
paddy@39 291 m.clientLock.Lock()
paddy@39 292 defer m.clientLock.Unlock()
paddy@39 293 c, ok := m.clients[id.String()]
paddy@39 294 if !ok {
paddy@39 295 return ErrClientNotFound
paddy@39 296 }
paddy@39 297 c.ApplyChange(change)
paddy@39 298 m.clients[id.String()] = c
paddy@31 299 return nil
paddy@31 300 }
paddy@31 301
paddy@57 302 func (m *memstore) deleteClient(id uuid.ID) error {
paddy@57 303 client, err := m.getClient(id)
paddy@31 304 if err != nil {
paddy@31 305 return err
paddy@31 306 }
paddy@31 307 m.clientLock.Lock()
paddy@31 308 defer m.clientLock.Unlock()
paddy@31 309 delete(m.clients, id.String())
paddy@31 310 pos := -1
paddy@31 311 for p, item := range m.profileClientLookup[client.OwnerID.String()] {
paddy@31 312 if item.Equal(id) {
paddy@31 313 pos = p
paddy@31 314 break
paddy@31 315 }
paddy@31 316 }
paddy@31 317 if pos >= 0 {
paddy@31 318 m.profileClientLookup[client.OwnerID.String()] = append(m.profileClientLookup[client.OwnerID.String()][:pos], m.profileClientLookup[client.OwnerID.String()][pos+1:]...)
paddy@31 319 }
paddy@31 320 return nil
paddy@31 321 }
paddy@31 322
paddy@57 323 func (m *memstore) listClientsByOwner(ownerID uuid.ID, num, offset int) ([]Client, error) {
paddy@33 324 ids := m.lookupClientsByProfileID(ownerID.String())
paddy@31 325 if len(ids) > num+offset {
paddy@31 326 ids = ids[offset : num+offset]
paddy@31 327 } else if len(ids) > offset {
paddy@31 328 ids = ids[offset:]
paddy@31 329 } else {
paddy@31 330 return []Client{}, nil
paddy@31 331 }
paddy@31 332 clients := []Client{}
paddy@31 333 for _, id := range ids {
paddy@57 334 client, err := m.getClient(id)
paddy@31 335 if err != nil {
paddy@31 336 return []Client{}, err
paddy@31 337 }
paddy@31 338 clients = append(clients, client)
paddy@31 339 }
paddy@31 340 return clients, nil
paddy@31 341 }
paddy@41 342
paddy@115 343 func (m *memstore) addEndpoints(client uuid.ID, endpoints []Endpoint) error {
paddy@41 344 m.endpointLock.Lock()
paddy@41 345 defer m.endpointLock.Unlock()
paddy@115 346 m.endpoints[client.String()] = append(m.endpoints[client.String()], endpoints...)
paddy@41 347 return nil
paddy@41 348 }
paddy@41 349
paddy@143 350 func (m *memstore) getEndpoint(client, endpoint uuid.ID) (Endpoint, error) {
paddy@143 351 m.endpointLock.Lock()
paddy@143 352 defer m.endpointLock.Unlock()
paddy@143 353 for _, item := range m.endpoints[client.String()] {
paddy@143 354 if item.ID.Equal(endpoint) {
paddy@143 355 return item, nil
paddy@143 356 }
paddy@143 357 }
paddy@143 358 return Endpoint{}, ErrEndpointNotFound
paddy@143 359 }
paddy@143 360
paddy@57 361 func (m *memstore) removeEndpoint(client, endpoint uuid.ID) error {
paddy@41 362 m.endpointLock.Lock()
paddy@41 363 defer m.endpointLock.Unlock()
paddy@41 364 pos := -1
paddy@41 365 for p, item := range m.endpoints[client.String()] {
paddy@41 366 if item.ID.Equal(endpoint) {
paddy@41 367 pos = p
paddy@41 368 break
paddy@41 369 }
paddy@41 370 }
paddy@41 371 if pos >= 0 {
paddy@41 372 m.endpoints[client.String()] = append(m.endpoints[client.String()][:pos], m.endpoints[client.String()][pos+1:]...)
paddy@41 373 }
paddy@41 374 return nil
paddy@41 375 }
paddy@41 376
paddy@58 377 func (m *memstore) checkEndpoint(client uuid.ID, endpoint string) (bool, error) {
paddy@41 378 m.endpointLock.RLock()
paddy@41 379 defer m.endpointLock.RUnlock()
paddy@41 380 for _, candidate := range m.endpoints[client.String()] {
paddy@116 381 if endpoint == candidate.NormalizedURI {
paddy@41 382 return true, nil
paddy@41 383 }
paddy@41 384 }
paddy@41 385 return false, nil
paddy@41 386 }
paddy@41 387
paddy@57 388 func (m *memstore) listEndpoints(client uuid.ID, num, offset int) ([]Endpoint, error) {
paddy@41 389 m.endpointLock.RLock()
paddy@41 390 defer m.endpointLock.RUnlock()
paddy@41 391 return m.endpoints[client.String()], nil
paddy@41 392 }
paddy@54 393
paddy@57 394 func (m *memstore) countEndpoints(client uuid.ID) (int64, error) {
paddy@54 395 m.endpointLock.RLock()
paddy@54 396 defer m.endpointLock.RUnlock()
paddy@54 397 return int64(len(m.endpoints[client.String()])), nil
paddy@54 398 }
paddy@108 399
paddy@108 400 type newClientReq struct {
paddy@108 401 Name string `json:"name"`
paddy@108 402 Logo string `json:"logo"`
paddy@108 403 Website string `json:"website"`
paddy@108 404 Type string `json:"type"`
paddy@108 405 Endpoints []string `json:"endpoints"`
paddy@108 406 }
paddy@108 407
paddy@108 408 func RegisterClientHandlers(r *mux.Router, context Context) {
paddy@108 409 r.Handle("/clients", wrap(context, CreateClientHandler)).Methods("POST")
paddy@131 410 r.Handle("/clients", wrap(context, ListClientsHandler)).Methods("GET")
paddy@131 411 r.Handle("/clients/{id}", wrap(context, GetClientHandler)).Methods("GET")
paddy@133 412 r.Handle("/clients/{id}", wrap(context, UpdateClientHandler)).Methods("PATCH")
paddy@144 413 r.Handle("/clients/{id}", wrap(context, RemoveClientHandler)).Methods("DELETE")
paddy@137 414 r.Handle("/clients/{id}/endpoints", wrap(context, AddEndpointsHandler)).Methods("POST")
paddy@144 415 r.Handle("/clients/{client_id}/endpoints/{id}", wrap(context, RemoveEndpointHandler)).Methods("DELETE")
paddy@138 416 r.Handle("/clients/{id}/endpoints", wrap(context, ListEndpointsHandler)).Methods("GET")
paddy@108 417 }
paddy@108 418
paddy@108 419 func CreateClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@115 420 errors := []requestError{}
paddy@108 421 username, password, ok := r.BasicAuth()
paddy@108 422 if !ok {
paddy@115 423 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@115 424 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@108 425 return
paddy@108 426 }
paddy@108 427 profile, err := authenticate(username, password, c)
paddy@108 428 if err != nil {
paddy@139 429 if isAuthError(err) {
paddy@139 430 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@139 431 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@139 432 } else {
paddy@139 433 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@139 434 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@139 435 }
paddy@108 436 return
paddy@108 437 }
paddy@108 438 var req newClientReq
paddy@108 439 decoder := json.NewDecoder(r.Body)
paddy@108 440 err = decoder.Decode(&req)
paddy@108 441 if err != nil {
paddy@108 442 encode(w, r, http.StatusBadRequest, invalidFormatResponse)
paddy@108 443 return
paddy@108 444 }
paddy@116 445 if req.Type == "" {
paddy@116 446 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/type"})
paddy@116 447 } else if req.Type != clientTypePublic && req.Type != clientTypeConfidential {
paddy@115 448 errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/type"})
paddy@116 449 }
paddy@116 450 if req.Name == "" {
paddy@116 451 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/name"})
paddy@116 452 } else if len(req.Name) < minClientNameLen {
paddy@116 453 errors = append(errors, requestError{Slug: requestErrInsufficient, Field: "/name"})
paddy@116 454 } else if len(req.Name) > maxClientNameLen {
paddy@116 455 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/name"})
paddy@116 456 }
paddy@116 457 if len(errors) > 0 {
paddy@115 458 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@108 459 return
paddy@108 460 }
paddy@108 461 client := Client{
paddy@108 462 ID: uuid.NewID(),
paddy@108 463 OwnerID: profile.ID,
paddy@108 464 Name: req.Name,
paddy@108 465 Logo: req.Logo,
paddy@108 466 Website: req.Website,
paddy@108 467 Type: req.Type,
paddy@108 468 }
paddy@118 469 if client.Type == clientTypeConfidential {
paddy@115 470 secret := make([]byte, 32)
paddy@115 471 _, err = rand.Read(secret)
paddy@115 472 if err != nil {
paddy@115 473 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
paddy@115 474 return
paddy@115 475 }
paddy@115 476 client.Secret = hex.EncodeToString(secret)
paddy@115 477 }
paddy@108 478 err = c.SaveClient(client)
paddy@108 479 if err != nil {
paddy@115 480 if err == ErrClientAlreadyExists {
paddy@115 481 errors = append(errors, requestError{Slug: requestErrConflict, Field: "/id"})
paddy@115 482 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@115 483 return
paddy@115 484 }
paddy@115 485 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
paddy@108 486 return
paddy@108 487 }
paddy@108 488 endpoints := []Endpoint{}
paddy@115 489 for pos, u := range req.Endpoints {
paddy@108 490 uri, err := url.Parse(u)
paddy@108 491 if err != nil {
paddy@115 492 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/endpoints/" + strconv.Itoa(pos)})
paddy@108 493 continue
paddy@108 494 }
paddy@116 495 if !uri.IsAbs() {
paddy@116 496 errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/endpoints/" + strconv.Itoa(pos)})
paddy@116 497 continue
paddy@116 498 }
paddy@108 499 endpoint := Endpoint{
paddy@108 500 ID: uuid.NewID(),
paddy@108 501 ClientID: client.ID,
paddy@116 502 URI: uri.String(),
paddy@108 503 Added: time.Now(),
paddy@108 504 }
paddy@108 505 endpoints = append(endpoints, endpoint)
paddy@108 506 }
paddy@115 507 err = c.AddEndpoints(client.ID, endpoints)
paddy@115 508 if err != nil {
paddy@115 509 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@115 510 encode(w, r, http.StatusInternalServerError, response{Errors: errors, Clients: []Client{client}})
paddy@115 511 return
paddy@115 512 }
paddy@108 513 resp := response{
paddy@108 514 Clients: []Client{client},
paddy@108 515 Endpoints: endpoints,
paddy@116 516 Errors: errors,
paddy@108 517 }
paddy@108 518 encode(w, r, http.StatusCreated, resp)
paddy@108 519 }
paddy@121 520
paddy@131 521 func GetClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@131 522 errors := []requestError{}
paddy@131 523 vars := mux.Vars(r)
paddy@131 524 if vars["id"] == "" {
paddy@131 525 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
paddy@131 526 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@131 527 return
paddy@131 528 }
paddy@131 529 id, err := uuid.Parse(vars["id"])
paddy@131 530 if err != nil {
paddy@131 531 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
paddy@131 532 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@131 533 return
paddy@131 534 }
paddy@131 535 client, err := c.GetClient(id)
paddy@131 536 if err != nil {
paddy@131 537 if err == ErrClientNotFound {
paddy@131 538 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
paddy@139 539 encode(w, r, http.StatusNotFound, response{Errors: errors})
paddy@131 540 return
paddy@131 541 }
paddy@131 542 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@131 543 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@131 544 return
paddy@131 545 }
paddy@139 546 username, password, ok := r.BasicAuth()
paddy@139 547 if !ok {
paddy@139 548 client.Secret = ""
paddy@139 549 } else {
paddy@139 550 profile, err := authenticate(username, password, c)
paddy@139 551 if err != nil {
paddy@139 552 if isAuthError(err) {
paddy@139 553 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@139 554 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@139 555 } else {
paddy@139 556 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@139 557 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@139 558 }
paddy@139 559 return
paddy@139 560 }
paddy@139 561 if !client.OwnerID.Equal(profile.ID) {
paddy@139 562 client.Secret = ""
paddy@139 563 }
paddy@139 564 }
paddy@131 565 resp := response{
paddy@131 566 Clients: []Client{client},
paddy@131 567 Errors: errors,
paddy@131 568 }
paddy@131 569 encode(w, r, http.StatusOK, resp)
paddy@131 570 }
paddy@131 571
paddy@131 572 func ListClientsHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@131 573 errors := []requestError{}
paddy@131 574 var err error
paddy@131 575 // BUG(paddy): If ids are provided in query params, retrieve only those clients
paddy@131 576 num := defaultClientResponseSize
paddy@131 577 offset := 0
paddy@131 578 ownerIDStr := r.URL.Query().Get("owner_id")
paddy@131 579 numStr := r.URL.Query().Get("num")
paddy@131 580 offsetStr := r.URL.Query().Get("offset")
paddy@131 581 if numStr != "" {
paddy@131 582 num, err = strconv.Atoi(numStr)
paddy@131 583 if err != nil {
paddy@131 584 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "num"})
paddy@131 585 }
paddy@131 586 if num > maxClientResponseSize {
paddy@131 587 errors = append(errors, requestError{Slug: requestErrOverflow, Param: "num"})
paddy@131 588 }
paddy@131 589 }
paddy@131 590 if offsetStr != "" {
paddy@131 591 offset, err = strconv.Atoi(offsetStr)
paddy@131 592 if err != nil {
paddy@131 593 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "offset"})
paddy@131 594 }
paddy@131 595 }
paddy@131 596 if ownerIDStr == "" {
paddy@131 597 errors = append(errors, requestError{Slug: requestErrMissing, Param: "owner_id"})
paddy@131 598 }
paddy@131 599 if len(errors) > 0 {
paddy@131 600 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@131 601 return
paddy@131 602 }
paddy@131 603 ownerID, err := uuid.Parse(ownerIDStr)
paddy@131 604 if err != nil {
paddy@131 605 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "owner_id"})
paddy@131 606 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@131 607 return
paddy@131 608 }
paddy@131 609 clients, err := c.ListClientsByOwner(ownerID, num, offset)
paddy@131 610 if err != nil {
paddy@131 611 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@131 612 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@131 613 return
paddy@131 614 }
paddy@140 615 username, password, ok := r.BasicAuth()
paddy@140 616 if !ok {
paddy@140 617 for pos, client := range clients {
paddy@140 618 client.Secret = ""
paddy@140 619 clients[pos] = client
paddy@140 620 }
paddy@140 621 } else {
paddy@140 622 profile, err := authenticate(username, password, c)
paddy@140 623 if err != nil {
paddy@140 624 if isAuthError(err) {
paddy@140 625 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@140 626 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@140 627 } else {
paddy@140 628 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@140 629 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@140 630 }
paddy@140 631 return
paddy@140 632 }
paddy@140 633 for pos, client := range clients {
paddy@140 634 if !client.OwnerID.Equal(profile.ID) {
paddy@140 635 client.Secret = ""
paddy@140 636 clients[pos] = client
paddy@140 637 }
paddy@140 638 }
paddy@131 639 }
paddy@131 640 resp := response{
paddy@131 641 Clients: clients,
paddy@131 642 Errors: errors,
paddy@131 643 }
paddy@131 644 encode(w, r, http.StatusOK, resp)
paddy@131 645 }
paddy@131 646
paddy@133 647 func UpdateClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@133 648 errors := []requestError{}
paddy@133 649 vars := mux.Vars(r)
paddy@133 650 if _, ok := vars["id"]; !ok {
paddy@133 651 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
paddy@133 652 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@133 653 return
paddy@133 654 }
paddy@141 655 id, err := uuid.Parse(vars["id"])
paddy@141 656 if err != nil {
paddy@141 657 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
paddy@141 658 }
paddy@141 659 username, password, ok := r.BasicAuth()
paddy@141 660 if !ok {
paddy@141 661 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@141 662 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@141 663 return
paddy@141 664 }
paddy@141 665 profile, err := authenticate(username, password, c)
paddy@141 666 if err != nil {
paddy@141 667 if isAuthError(err) {
paddy@141 668 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@141 669 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@141 670 } else {
paddy@141 671 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@141 672 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@141 673 }
paddy@141 674 return
paddy@141 675 }
paddy@133 676 var change ClientChange
paddy@141 677 err = decode(r, &change)
paddy@133 678 if err != nil {
paddy@133 679 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/"})
paddy@133 680 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@133 681 return
paddy@133 682 }
paddy@133 683 errs := change.Validate()
paddy@133 684 for _, err := range errs {
paddy@133 685 switch err {
paddy@133 686 case ErrEmptyChange:
paddy@133 687 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/"})
paddy@133 688 case ErrClientNameTooShort:
paddy@133 689 errors = append(errors, requestError{Slug: requestErrInsufficient, Field: "/name"})
paddy@133 690 case ErrClientNameTooLong:
paddy@133 691 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/name"})
paddy@133 692 case ErrClientLogoTooLong:
paddy@133 693 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/logo"})
paddy@133 694 case ErrClientLogoNotURL:
paddy@133 695 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/logo"})
paddy@133 696 case ErrClientWebsiteTooLong:
paddy@133 697 errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/website"})
paddy@133 698 case ErrClientWebsiteNotURL:
paddy@133 699 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/website"})
paddy@133 700 default:
paddy@133 701 log.Println("Unrecognised error from client change validation:", err)
paddy@133 702 }
paddy@133 703 }
paddy@133 704 if len(errors) > 0 {
paddy@133 705 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@133 706 return
paddy@133 707 }
paddy@133 708 client, err := c.GetClient(id)
paddy@133 709 if err == ErrClientNotFound {
paddy@133 710 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
paddy@133 711 encode(w, r, http.StatusNotFound, response{Errors: errors})
paddy@133 712 return
paddy@133 713 } else if err != nil {
paddy@133 714 log.Println("Error retrieving client:", err)
paddy@133 715 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@133 716 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@133 717 return
paddy@133 718 }
paddy@141 719 if !client.OwnerID.Equal(profile.ID) {
paddy@141 720 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@141 721 encode(w, r, http.StatusForbidden, response{Errors: errors})
paddy@141 722 return
paddy@141 723 }
paddy@133 724 if change.Secret != nil && client.Type == clientTypeConfidential {
paddy@133 725 secret := make([]byte, 32)
paddy@133 726 _, err = rand.Read(secret)
paddy@133 727 if err != nil {
paddy@133 728 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
paddy@133 729 return
paddy@133 730 }
paddy@133 731 newSecret := hex.EncodeToString(secret)
paddy@133 732 change.Secret = &newSecret
paddy@133 733 }
paddy@133 734 err = c.UpdateClient(id, change)
paddy@133 735 if err != nil {
paddy@133 736 log.Println("Error updating client:", err)
paddy@133 737 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@133 738 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@133 739 return
paddy@133 740 }
paddy@133 741 client.ApplyChange(change)
paddy@133 742 encode(w, r, http.StatusOK, response{Clients: []Client{client}, Errors: errors})
paddy@133 743 return
paddy@133 744 }
paddy@133 745
paddy@144 746 func RemoveClientHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@144 747 errors := []requestError{}
paddy@144 748 vars := mux.Vars(r)
paddy@144 749 if _, ok := vars["id"]; !ok {
paddy@144 750 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
paddy@144 751 encode(w, r, http.StatusNotFound, response{Errors: errors})
paddy@144 752 return
paddy@144 753 }
paddy@144 754 id, err := uuid.Parse(vars["id"])
paddy@144 755 if err != nil {
paddy@144 756 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
paddy@144 757 }
paddy@144 758 username, password, ok := r.BasicAuth()
paddy@144 759 if !ok {
paddy@144 760 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@144 761 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@144 762 return
paddy@144 763 }
paddy@144 764 profile, err := authenticate(username, password, c)
paddy@144 765 if err != nil {
paddy@144 766 if isAuthError(err) {
paddy@144 767 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@144 768 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@144 769 } else {
paddy@144 770 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@144 771 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@144 772 }
paddy@144 773 return
paddy@144 774 }
paddy@144 775 client, err := c.GetClient(id)
paddy@144 776 if err != nil {
paddy@144 777 if err == ErrClientNotFound {
paddy@144 778 errors = append(errors, requestError{Slug: requestErrNotFound})
paddy@144 779 encode(w, r, http.StatusNotFound, response{Errors: errors})
paddy@144 780 return
paddy@144 781 }
paddy@144 782 log.Println("Error retrieving client:", err)
paddy@144 783 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@144 784 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@144 785 return
paddy@144 786 }
paddy@144 787 if !client.OwnerID.Equal(profile.ID) {
paddy@144 788 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@144 789 encode(w, r, http.StatusForbidden, response{Errors: errors})
paddy@144 790 return
paddy@144 791 }
paddy@144 792 err = c.DeleteClient(id)
paddy@144 793 if err != nil {
paddy@144 794 if err == ErrClientNotFound {
paddy@144 795 errors = append(errors, requestError{Slug: requestErrNotFound})
paddy@144 796 encode(w, r, http.StatusNotFound, response{Errors: errors})
paddy@144 797 return
paddy@144 798 }
paddy@144 799 log.Println("Error deleting client:", err)
paddy@144 800 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@144 801 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@144 802 return
paddy@144 803 }
paddy@144 804 // BUG(paddy): Client needs to clean up after itself, invalidating tokens, deleting unused grants, deleting endpoints
paddy@144 805 encode(w, r, http.StatusOK, response{Errors: errors})
paddy@144 806 return
paddy@144 807 }
paddy@144 808
paddy@137 809 func AddEndpointsHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@137 810 type addEndpointReq struct {
paddy@137 811 Endpoints []string `json:"endpoints"`
paddy@137 812 }
paddy@137 813 errors := []requestError{}
paddy@137 814 vars := mux.Vars(r)
paddy@137 815 if vars["id"] == "" {
paddy@137 816 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
paddy@137 817 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@137 818 return
paddy@137 819 }
paddy@137 820 id, err := uuid.Parse(vars["id"])
paddy@137 821 if err != nil {
paddy@137 822 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
paddy@137 823 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@137 824 return
paddy@137 825 }
paddy@142 826 username, password, ok := r.BasicAuth()
paddy@142 827 if !ok {
paddy@142 828 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@142 829 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@142 830 return
paddy@142 831 }
paddy@142 832 profile, err := authenticate(username, password, c)
paddy@142 833 if err != nil {
paddy@142 834 if isAuthError(err) {
paddy@142 835 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@142 836 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@142 837 } else {
paddy@142 838 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@142 839 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@142 840 }
paddy@142 841 return
paddy@142 842 }
paddy@143 843 client, err := c.GetClient(id)
paddy@137 844 if err != nil {
paddy@137 845 if err == ErrClientNotFound {
paddy@137 846 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
paddy@137 847 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@137 848 return
paddy@137 849 }
paddy@137 850 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@137 851 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@137 852 return
paddy@137 853 }
paddy@142 854 if !client.OwnerID.Equal(profile.ID) {
paddy@142 855 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@142 856 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@142 857 return
paddy@142 858 }
paddy@137 859 var req addEndpointReq
paddy@137 860 decoder := json.NewDecoder(r.Body)
paddy@137 861 err = decoder.Decode(&req)
paddy@137 862 if err != nil {
paddy@137 863 encode(w, r, http.StatusBadRequest, invalidFormatResponse)
paddy@137 864 return
paddy@137 865 }
paddy@137 866 if len(req.Endpoints) < 1 {
paddy@137 867 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/endpoints"})
paddy@137 868 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@137 869 return
paddy@137 870 }
paddy@137 871 endpoints := []Endpoint{}
paddy@137 872 for pos, u := range req.Endpoints {
paddy@137 873 if parsed, err := url.Parse(u); err != nil {
paddy@137 874 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/endpoints/" + strconv.Itoa(pos)})
paddy@137 875 continue
paddy@137 876 } else if !parsed.IsAbs() {
paddy@137 877 errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/endpoints" + strconv.Itoa(pos)})
paddy@137 878 continue
paddy@137 879 }
paddy@137 880 e := Endpoint{
paddy@137 881 ID: uuid.NewID(),
paddy@137 882 ClientID: id,
paddy@137 883 URI: u,
paddy@137 884 Added: time.Now(),
paddy@137 885 }
paddy@137 886 endpoints = append(endpoints, e)
paddy@137 887 }
paddy@137 888 if len(errors) > 0 {
paddy@137 889 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@137 890 return
paddy@137 891 }
paddy@137 892 err = c.AddEndpoints(id, endpoints)
paddy@137 893 if err != nil {
paddy@137 894 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
paddy@137 895 return
paddy@137 896 }
paddy@137 897 resp := response{
paddy@137 898 Errors: errors,
paddy@137 899 Endpoints: endpoints,
paddy@137 900 }
paddy@137 901 encode(w, r, http.StatusCreated, resp)
paddy@137 902 }
paddy@137 903
paddy@138 904 func ListEndpointsHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@138 905 errors := []requestError{}
paddy@138 906 vars := mux.Vars(r)
paddy@138 907 clientID, err := uuid.Parse(vars["id"])
paddy@138 908 if err != nil {
paddy@138 909 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "client_id"})
paddy@138 910 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@138 911 return
paddy@138 912 }
paddy@138 913 num := defaultEndpointResponseSize
paddy@138 914 offset := 0
paddy@138 915 numStr := r.URL.Query().Get("num")
paddy@138 916 offsetStr := r.URL.Query().Get("offset")
paddy@138 917 if numStr != "" {
paddy@138 918 num, err = strconv.Atoi(numStr)
paddy@138 919 if err != nil {
paddy@138 920 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "num"})
paddy@138 921 }
paddy@138 922 if num > maxEndpointResponseSize {
paddy@138 923 errors = append(errors, requestError{Slug: requestErrOverflow, Param: "num"})
paddy@138 924 }
paddy@138 925 }
paddy@138 926 if offsetStr != "" {
paddy@138 927 offset, err = strconv.Atoi(offsetStr)
paddy@138 928 if err != nil {
paddy@138 929 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "offset"})
paddy@138 930 }
paddy@138 931 }
paddy@138 932 if len(errors) > 0 {
paddy@138 933 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@138 934 return
paddy@138 935 }
paddy@138 936 endpoints, err := c.ListEndpoints(clientID, num, offset)
paddy@138 937 if err != nil {
paddy@138 938 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@138 939 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@138 940 return
paddy@138 941 }
paddy@138 942 resp := response{
paddy@138 943 Endpoints: endpoints,
paddy@138 944 Errors: errors,
paddy@138 945 }
paddy@138 946 encode(w, r, http.StatusOK, resp)
paddy@138 947 }
paddy@138 948
paddy@143 949 func RemoveEndpointHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@143 950 errors := []requestError{}
paddy@143 951 vars := mux.Vars(r)
paddy@143 952 if vars["client_id"] == "" {
paddy@143 953 errors = append(errors, requestError{Slug: requestErrMissing, Param: "client_id"})
paddy@143 954 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 955 return
paddy@143 956 }
paddy@143 957 clientID, err := uuid.Parse(vars["client_id"])
paddy@143 958 if err != nil {
paddy@143 959 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "client_id"})
paddy@143 960 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 961 return
paddy@143 962 }
paddy@143 963 if vars["id"] == "" {
paddy@143 964 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
paddy@143 965 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 966 return
paddy@143 967 }
paddy@143 968 id, err := uuid.Parse(vars["id"])
paddy@143 969 if err != nil {
paddy@143 970 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
paddy@143 971 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 972 return
paddy@143 973 }
paddy@143 974 username, password, ok := r.BasicAuth()
paddy@143 975 if !ok {
paddy@143 976 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@143 977 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@143 978 return
paddy@143 979 }
paddy@143 980 profile, err := authenticate(username, password, c)
paddy@143 981 if err != nil {
paddy@143 982 if isAuthError(err) {
paddy@143 983 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@143 984 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@143 985 } else {
paddy@143 986 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@143 987 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@143 988 }
paddy@143 989 return
paddy@143 990 }
paddy@143 991 client, err := c.GetClient(clientID)
paddy@143 992 if err != nil {
paddy@143 993 if err == ErrClientNotFound {
paddy@143 994 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "client_id"})
paddy@143 995 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 996 return
paddy@143 997 }
paddy@143 998 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@143 999 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@143 1000 return
paddy@143 1001 }
paddy@143 1002 if !client.OwnerID.Equal(profile.ID) {
paddy@143 1003 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@143 1004 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@143 1005 return
paddy@143 1006 }
paddy@143 1007 endpoint, err := c.GetEndpoint(clientID, id)
paddy@143 1008 if err != nil {
paddy@143 1009 if err == ErrEndpointNotFound {
paddy@143 1010 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
paddy@143 1011 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 1012 return
paddy@143 1013 }
paddy@143 1014 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@143 1015 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@143 1016 return
paddy@143 1017 }
paddy@143 1018 err = c.RemoveEndpoint(clientID, id)
paddy@143 1019 if err != nil {
paddy@143 1020 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
paddy@143 1021 return
paddy@143 1022 }
paddy@143 1023 resp := response{
paddy@143 1024 Errors: errors,
paddy@143 1025 Endpoints: []Endpoint{endpoint},
paddy@143 1026 }
paddy@143 1027 encode(w, r, http.StatusCreated, resp)
paddy@143 1028 }
paddy@143 1029
paddy@135 1030 func clientCredentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes []string, profileID uuid.ID, valid bool) {
paddy@135 1031 scopes = strings.Split(r.PostFormValue("scope"), " ")
paddy@121 1032 valid = true
paddy@121 1033 return
paddy@121 1034 }
paddy@124 1035
paddy@124 1036 func clientCredentialsAuditString(r *http.Request) string {
paddy@124 1037 return "client_credentials"
paddy@124 1038 }