auth

Paddy 2015-12-14 Parent:b7e685839a1b

182:cd5f07f9811b Go to Latest

auth/client.go

Update nsq import path. go-nsq has moved to nsqio/go-nsq, so we need to update the import path appropriately.

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