auth

Paddy 2015-04-11 Parent:77db7c65216c Child:73e12d5a1124

157:202e991accc2 Go to Latest

auth/client.go

Wire up the postgres database for authd. Have authd use the AUTH_PG_DB environment variable to detect support for the postgres *Stores, and if postgres is supported, use it. If postgres isn't supported, fall back on the in-memory store. Also create-if-not-exists the test scopes, instead of panicking when the scope already exists.

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