func testClientCRUD(t *testing.T, s storage.Storage) { id := storage.NewID() c := storage.Client{ ID: id, Secret: "foobar", RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"}, Name: "dex client", LogoURL: "https://goo.gl/JIyzIC", } err := s.DeleteClient(id) mustBeErrNotFound(t, "client", err) if err := s.CreateClient(c); err != nil { t.Fatalf("create client: %v", err) } getAndCompare := func(id string, want storage.Client) { gc, err := s.GetClient(id) if err != nil { t.Errorf("get client: %v", err) return } if diff := pretty.Compare(want, gc); diff != "" { t.Errorf("client retrieved from storage did not match: %s", diff) } } getAndCompare(id, c) newSecret := "barfoo" err = s.UpdateClient(id, func(old storage.Client) (storage.Client, error) { old.Secret = newSecret return old, nil }) if err != nil { t.Errorf("update client: %v", err) } c.Secret = newSecret getAndCompare(id, c) if err := s.DeleteClient(id); err != nil { t.Fatalf("delete client: %v", err) } _, err = s.GetClient(id) mustBeErrNotFound(t, "client", err) }
func validateCrossClientTrust(s storage.Storage, clientID, peerID string) (trusted bool, err error) { if peerID == clientID { return true, nil } peer, err := s.GetClient(peerID) if err != nil { if err != storage.ErrNotFound { log.Printf("Failed to get client: %v", err) return false, err } return false, nil } for _, id := range peer.TrustedPeers { if id == clientID { return true, nil } } return false, nil }
// parse the initial request from the OAuth2 client. // // For correctness the logic is largely copied from https://github.com/RangelReale/osin. func parseAuthorizationRequest(s storage.Storage, supportedResponseTypes map[string]bool, r *http.Request) (req storage.AuthRequest, oauth2Err *authErr) { if err := r.ParseForm(); err != nil { return req, &authErr{"", "", errInvalidRequest, "Failed to parse request."} } redirectURI, err := url.QueryUnescape(r.Form.Get("redirect_uri")) if err != nil { return req, &authErr{"", "", errInvalidRequest, "No redirect_uri provided."} } state := r.FormValue("state") clientID := r.Form.Get("client_id") client, err := s.GetClient(clientID) if err != nil { if err == storage.ErrNotFound { description := fmt.Sprintf("Invalid client_id (%q).", clientID) return req, &authErr{"", "", errUnauthorizedClient, description} } log.Printf("Failed to get client: %v", err) return req, &authErr{"", "", errServerError, ""} } if !validateRedirectURI(client, redirectURI) { description := fmt.Sprintf("Unregistered redirect_uri (%q).", redirectURI) return req, &authErr{"", "", errInvalidRequest, description} } newErr := func(typ, format string, a ...interface{}) *authErr { return &authErr{state, redirectURI, typ, fmt.Sprintf(format, a...)} } scopes := strings.Fields(r.Form.Get("scope")) var ( unrecognized []string invalidScopes []string ) hasOpenIDScope := false for _, scope := range scopes { switch scope { case scopeOpenID: hasOpenIDScope = true case scopeOfflineAccess, scopeEmail, scopeProfile, scopeGroups: default: peerID, ok := parseCrossClientScope(scope) if !ok { unrecognized = append(unrecognized, scope) continue } isTrusted, err := validateCrossClientTrust(s, clientID, peerID) if err != nil { return req, newErr(errServerError, "") } if !isTrusted { invalidScopes = append(invalidScopes, scope) } } } if !hasOpenIDScope { return req, newErr("invalid_scope", `Missing required scope(s) ["openid"].`) } if len(unrecognized) > 0 { return req, newErr("invalid_scope", "Unrecognized scope(s) %q", unrecognized) } if len(invalidScopes) > 0 { return req, newErr("invalid_scope", "Client can't request scope(s) %q", invalidScopes) } nonce := r.Form.Get("nonce") responseTypes := strings.Split(r.Form.Get("response_type"), " ") for _, responseType := range responseTypes { if !supportedResponseTypes[responseType] { return req, newErr("invalid_request", "Invalid response type %q", responseType) } switch responseType { case responseTypeCode: case responseTypeToken: // Implicit flow requires a nonce value. // https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest if nonce == "" { return req, newErr("invalid_request", "Response type 'token' requires a 'nonce' value.") } if redirectURI == redirectURIOOB { err := fmt.Sprintf("Cannot use response type 'token' with redirect_uri '%s'.", redirectURIOOB) return req, newErr("invalid_request", err) } default: return req, newErr("invalid_request", "Invalid response type %q", responseType) } } return storage.AuthRequest{ ID: storage.NewID(), ClientID: client.ID, State: r.Form.Get("state"), Nonce: nonce, ForceApprovalPrompt: r.Form.Get("approval_prompt") == "force", Scopes: scopes, RedirectURI: redirectURI, ResponseTypes: responseTypes, }, nil }