auth

Paddy 2015-03-21 Parent:8267e1c8bcd1 Child:77db7c65216c

150:379702564771 Go to Latest

auth/client.go

Fix whitespace in Profile queries. The lack of whitespace around the ` = ?` expression would have bothered me, so I fixed it.

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