auth

Paddy 2015-03-07 Parent:f1c8e13e1ce6 Child:93c758f57c69

143:3aeadd2201e9 Go to Latest

auth/client.go

Add GetEndpoint method, add RemoveEndpoint handler. Add a method to our clientStore and Context types for retrieving an Endpoint from the clientStore. We need this to tell if someone is trying to delete an Endpoint that doesn't exist. Add a handler for removing Endpoints from a Client.

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@128 413 // BUG(paddy): We need to implement a handler to delete a client. Also, what should that do with the grants and tokens belonging to that client?
paddy@137 414 r.Handle("/clients/{id}/endpoints", wrap(context, AddEndpointsHandler)).Methods("POST")
paddy@128 415 // BUG(paddy): We need to implement a handler to remove an endpoint from a client.
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@137 746 func AddEndpointsHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@137 747 type addEndpointReq struct {
paddy@137 748 Endpoints []string `json:"endpoints"`
paddy@137 749 }
paddy@137 750 errors := []requestError{}
paddy@137 751 vars := mux.Vars(r)
paddy@137 752 if vars["id"] == "" {
paddy@137 753 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
paddy@137 754 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@137 755 return
paddy@137 756 }
paddy@137 757 id, err := uuid.Parse(vars["id"])
paddy@137 758 if err != nil {
paddy@137 759 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
paddy@137 760 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@137 761 return
paddy@137 762 }
paddy@142 763 username, password, ok := r.BasicAuth()
paddy@142 764 if !ok {
paddy@142 765 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@142 766 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@142 767 return
paddy@142 768 }
paddy@142 769 profile, err := authenticate(username, password, c)
paddy@142 770 if err != nil {
paddy@142 771 if isAuthError(err) {
paddy@142 772 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@142 773 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@142 774 } else {
paddy@142 775 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@142 776 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@142 777 }
paddy@142 778 return
paddy@142 779 }
paddy@143 780 client, err := c.GetClient(id)
paddy@137 781 if err != nil {
paddy@137 782 if err == ErrClientNotFound {
paddy@137 783 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
paddy@137 784 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@137 785 return
paddy@137 786 }
paddy@137 787 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@137 788 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@137 789 return
paddy@137 790 }
paddy@142 791 if !client.OwnerID.Equal(profile.ID) {
paddy@142 792 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@142 793 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@142 794 return
paddy@142 795 }
paddy@137 796 var req addEndpointReq
paddy@137 797 decoder := json.NewDecoder(r.Body)
paddy@137 798 err = decoder.Decode(&req)
paddy@137 799 if err != nil {
paddy@137 800 encode(w, r, http.StatusBadRequest, invalidFormatResponse)
paddy@137 801 return
paddy@137 802 }
paddy@137 803 if len(req.Endpoints) < 1 {
paddy@137 804 errors = append(errors, requestError{Slug: requestErrMissing, Field: "/endpoints"})
paddy@137 805 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@137 806 return
paddy@137 807 }
paddy@137 808 endpoints := []Endpoint{}
paddy@137 809 for pos, u := range req.Endpoints {
paddy@137 810 if parsed, err := url.Parse(u); err != nil {
paddy@137 811 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Field: "/endpoints/" + strconv.Itoa(pos)})
paddy@137 812 continue
paddy@137 813 } else if !parsed.IsAbs() {
paddy@137 814 errors = append(errors, requestError{Slug: requestErrInvalidValue, Field: "/endpoints" + strconv.Itoa(pos)})
paddy@137 815 continue
paddy@137 816 }
paddy@137 817 e := Endpoint{
paddy@137 818 ID: uuid.NewID(),
paddy@137 819 ClientID: id,
paddy@137 820 URI: u,
paddy@137 821 Added: time.Now(),
paddy@137 822 }
paddy@137 823 endpoints = append(endpoints, e)
paddy@137 824 }
paddy@137 825 if len(errors) > 0 {
paddy@137 826 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@137 827 return
paddy@137 828 }
paddy@137 829 err = c.AddEndpoints(id, endpoints)
paddy@137 830 if err != nil {
paddy@137 831 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
paddy@137 832 return
paddy@137 833 }
paddy@137 834 resp := response{
paddy@137 835 Errors: errors,
paddy@137 836 Endpoints: endpoints,
paddy@137 837 }
paddy@137 838 encode(w, r, http.StatusCreated, resp)
paddy@137 839 }
paddy@137 840
paddy@138 841 func ListEndpointsHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@138 842 errors := []requestError{}
paddy@138 843 vars := mux.Vars(r)
paddy@138 844 clientID, err := uuid.Parse(vars["id"])
paddy@138 845 if err != nil {
paddy@138 846 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "client_id"})
paddy@138 847 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@138 848 return
paddy@138 849 }
paddy@138 850 num := defaultEndpointResponseSize
paddy@138 851 offset := 0
paddy@138 852 numStr := r.URL.Query().Get("num")
paddy@138 853 offsetStr := r.URL.Query().Get("offset")
paddy@138 854 if numStr != "" {
paddy@138 855 num, err = strconv.Atoi(numStr)
paddy@138 856 if err != nil {
paddy@138 857 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "num"})
paddy@138 858 }
paddy@138 859 if num > maxEndpointResponseSize {
paddy@138 860 errors = append(errors, requestError{Slug: requestErrOverflow, Param: "num"})
paddy@138 861 }
paddy@138 862 }
paddy@138 863 if offsetStr != "" {
paddy@138 864 offset, err = strconv.Atoi(offsetStr)
paddy@138 865 if err != nil {
paddy@138 866 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "offset"})
paddy@138 867 }
paddy@138 868 }
paddy@138 869 if len(errors) > 0 {
paddy@138 870 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@138 871 return
paddy@138 872 }
paddy@138 873 endpoints, err := c.ListEndpoints(clientID, num, offset)
paddy@138 874 if err != nil {
paddy@138 875 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@138 876 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@138 877 return
paddy@138 878 }
paddy@138 879 resp := response{
paddy@138 880 Endpoints: endpoints,
paddy@138 881 Errors: errors,
paddy@138 882 }
paddy@138 883 encode(w, r, http.StatusOK, resp)
paddy@138 884 }
paddy@138 885
paddy@143 886 func RemoveEndpointHandler(w http.ResponseWriter, r *http.Request, c Context) {
paddy@143 887 errors := []requestError{}
paddy@143 888 vars := mux.Vars(r)
paddy@143 889 if vars["client_id"] == "" {
paddy@143 890 errors = append(errors, requestError{Slug: requestErrMissing, Param: "client_id"})
paddy@143 891 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 892 return
paddy@143 893 }
paddy@143 894 clientID, err := uuid.Parse(vars["client_id"])
paddy@143 895 if err != nil {
paddy@143 896 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "client_id"})
paddy@143 897 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 898 return
paddy@143 899 }
paddy@143 900 if vars["id"] == "" {
paddy@143 901 errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"})
paddy@143 902 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 903 return
paddy@143 904 }
paddy@143 905 id, err := uuid.Parse(vars["id"])
paddy@143 906 if err != nil {
paddy@143 907 errors = append(errors, requestError{Slug: requestErrInvalidFormat, Param: "id"})
paddy@143 908 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 909 return
paddy@143 910 }
paddy@143 911 username, password, ok := r.BasicAuth()
paddy@143 912 if !ok {
paddy@143 913 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@143 914 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@143 915 return
paddy@143 916 }
paddy@143 917 profile, err := authenticate(username, password, c)
paddy@143 918 if err != nil {
paddy@143 919 if isAuthError(err) {
paddy@143 920 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@143 921 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@143 922 } else {
paddy@143 923 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@143 924 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@143 925 }
paddy@143 926 return
paddy@143 927 }
paddy@143 928 client, err := c.GetClient(clientID)
paddy@143 929 if err != nil {
paddy@143 930 if err == ErrClientNotFound {
paddy@143 931 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "client_id"})
paddy@143 932 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 933 return
paddy@143 934 }
paddy@143 935 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@143 936 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@143 937 return
paddy@143 938 }
paddy@143 939 if !client.OwnerID.Equal(profile.ID) {
paddy@143 940 errors = append(errors, requestError{Slug: requestErrAccessDenied})
paddy@143 941 encode(w, r, http.StatusUnauthorized, response{Errors: errors})
paddy@143 942 return
paddy@143 943 }
paddy@143 944 endpoint, err := c.GetEndpoint(clientID, id)
paddy@143 945 if err != nil {
paddy@143 946 if err == ErrEndpointNotFound {
paddy@143 947 errors = append(errors, requestError{Slug: requestErrNotFound, Param: "id"})
paddy@143 948 encode(w, r, http.StatusBadRequest, response{Errors: errors})
paddy@143 949 return
paddy@143 950 }
paddy@143 951 errors = append(errors, requestError{Slug: requestErrActOfGod})
paddy@143 952 encode(w, r, http.StatusInternalServerError, response{Errors: errors})
paddy@143 953 return
paddy@143 954 }
paddy@143 955 err = c.RemoveEndpoint(clientID, id)
paddy@143 956 if err != nil {
paddy@143 957 encode(w, r, http.StatusInternalServerError, actOfGodResponse)
paddy@143 958 return
paddy@143 959 }
paddy@143 960 resp := response{
paddy@143 961 Errors: errors,
paddy@143 962 Endpoints: []Endpoint{endpoint},
paddy@143 963 }
paddy@143 964 encode(w, r, http.StatusCreated, resp)
paddy@143 965 }
paddy@143 966
paddy@135 967 func clientCredentialsValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes []string, profileID uuid.ID, valid bool) {
paddy@135 968 scopes = strings.Split(r.PostFormValue("scope"), " ")
paddy@121 969 valid = true
paddy@121 970 return
paddy@121 971 }
paddy@124 972
paddy@124 973 func clientCredentialsAuditString(r *http.Request) string {
paddy@124 974 return "client_credentials"
paddy@124 975 }