Merge pull request #392 from ericchiang/admin_client_registration_2
add client registration to the admin API
This commit is contained in:
commit
de8f345642
17 changed files with 438 additions and 32 deletions
37
admin/api.go
37
admin/api.go
|
@ -4,6 +4,11 @@ package admin
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
"github.com/go-gorp/gorp"
|
||||
|
||||
"github.com/coreos/dex/client"
|
||||
"github.com/coreos/dex/db"
|
||||
"github.com/coreos/dex/schema/adminschema"
|
||||
"github.com/coreos/dex/user"
|
||||
"github.com/coreos/dex/user/manager"
|
||||
|
@ -14,18 +19,21 @@ type AdminAPI struct {
|
|||
userManager *manager.UserManager
|
||||
userRepo user.UserRepo
|
||||
passwordInfoRepo user.PasswordInfoRepo
|
||||
clientIdentityRepo client.ClientIdentityRepo
|
||||
localConnectorID string
|
||||
}
|
||||
|
||||
func NewAdminAPI(userManager *manager.UserManager, userRepo user.UserRepo, pwiRepo user.PasswordInfoRepo, localConnectorID string) *AdminAPI {
|
||||
// TODO(ericchiang): Swap the DbMap for a storage interface. See #278
|
||||
|
||||
func NewAdminAPI(dbMap *gorp.DbMap, userManager *manager.UserManager, localConnectorID string) *AdminAPI {
|
||||
if localConnectorID == "" {
|
||||
panic("must specify non-blank localConnectorID")
|
||||
}
|
||||
|
||||
return &AdminAPI{
|
||||
userManager: userManager,
|
||||
userRepo: userRepo,
|
||||
passwordInfoRepo: pwiRepo,
|
||||
userRepo: db.NewUserRepo(dbMap),
|
||||
passwordInfoRepo: db.NewPasswordInfoRepo(dbMap),
|
||||
clientIdentityRepo: db.NewClientIdentityRepo(dbMap),
|
||||
localConnectorID: localConnectorID,
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +116,27 @@ func (a *AdminAPI) GetState() (adminschema.State, error) {
|
|||
return state, nil
|
||||
}
|
||||
|
||||
type ClientRegistrationRequest struct {
|
||||
IsAdmin bool `json:"isAdmin"`
|
||||
Client oidc.ClientMetadata `json:"client"`
|
||||
}
|
||||
|
||||
func (a *AdminAPI) CreateClient(req ClientRegistrationRequest) (oidc.ClientRegistrationResponse, error) {
|
||||
if err := req.Client.Valid(); err != nil {
|
||||
return oidc.ClientRegistrationResponse{}, mapError(err)
|
||||
}
|
||||
// metadata is guarenteed to have at least one redirect_uri by earlier validation.
|
||||
id, err := oidc.GenClientID(req.Client.RedirectURIs[0].Host)
|
||||
if err != nil {
|
||||
return oidc.ClientRegistrationResponse{}, mapError(err)
|
||||
}
|
||||
c, err := a.clientIdentityRepo.New(id, req.Client, req.IsAdmin)
|
||||
if err != nil {
|
||||
return oidc.ClientRegistrationResponse{}, mapError(err)
|
||||
}
|
||||
return oidc.ClientRegistrationResponse{ClientID: c.ID, ClientSecret: c.Secret, ClientMetadata: req.Client}, nil
|
||||
}
|
||||
|
||||
func mapError(e error) error {
|
||||
if mapped, ok := errorMap[e]; ok {
|
||||
return mapped(e)
|
||||
|
|
|
@ -69,7 +69,7 @@ func makeTestFixtures() *testFixtures {
|
|||
}()
|
||||
|
||||
f.mgr = manager.NewUserManager(f.ur, f.pwr, ccr, db.TransactionFactory(dbMap), manager.ManagerOptions{})
|
||||
f.adAPI = NewAdminAPI(f.mgr, f.ur, f.pwr, "local")
|
||||
f.adAPI = NewAdminAPI(dbMap, f.mgr, "local")
|
||||
|
||||
return f
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ type ClientIdentityRepo interface {
|
|||
// New registers a ClientIdentity with the repo for the given metadata.
|
||||
// An unused ID must be provided. A corresponding secret will be returned
|
||||
// in a ClientCredentials struct along with the provided ID.
|
||||
New(id string, meta oidc.ClientMetadata) (*oidc.ClientCredentials, error)
|
||||
New(id string, meta oidc.ClientMetadata, admin bool) (*oidc.ClientCredentials, error)
|
||||
|
||||
SetDexAdmin(clientID string, isAdmin bool) error
|
||||
|
||||
|
|
|
@ -113,13 +113,13 @@ func main() {
|
|||
time.Sleep(sleep)
|
||||
}
|
||||
}
|
||||
|
||||
userRepo := db.NewUserRepo(dbc)
|
||||
pwiRepo := db.NewPasswordInfoRepo(dbc)
|
||||
connCfgRepo := db.NewConnectorConfigRepo(dbc)
|
||||
userManager := manager.NewUserManager(userRepo,
|
||||
pwiRepo, connCfgRepo, db.TransactionFactory(dbc), manager.ManagerOptions{})
|
||||
adminAPI := admin.NewAdminAPI(userManager, userRepo, pwiRepo, *localConnectorID)
|
||||
|
||||
adminAPI := admin.NewAdminAPI(dbc, userManager, *localConnectorID)
|
||||
kRepo, err := db.NewPrivateKeySetRepo(dbc, *useOldFormat, keySecrets.BytesSlice()...)
|
||||
if err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
|
|
|
@ -36,7 +36,7 @@ func (d *dbDriver) NewClient(meta oidc.ClientMetadata) (*oidc.ClientCredentials,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return d.ciRepo.New(clientID, meta)
|
||||
return d.ciRepo.New(clientID, meta, false)
|
||||
}
|
||||
|
||||
func (d *dbDriver) ConnectorConfigs() ([]connector.ConnectorConfig, error) {
|
||||
|
|
|
@ -234,7 +234,7 @@ func isAlreadyExistsErr(err error) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (r *clientIdentityRepo) New(id string, meta oidc.ClientMetadata) (*oidc.ClientCredentials, error) {
|
||||
func (r *clientIdentityRepo) New(id string, meta oidc.ClientMetadata, admin bool) (*oidc.ClientCredentials, error) {
|
||||
secret, err := pcrypto.RandBytes(maxSecretLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -244,6 +244,7 @@ func (r *clientIdentityRepo) New(id string, meta oidc.ClientMetadata) (*oidc.Cli
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cim.DexAdmin = admin
|
||||
|
||||
if err := r.executor(nil).Insert(cim); err != nil {
|
||||
if isAlreadyExistsErr(err) {
|
||||
|
|
|
@ -191,7 +191,7 @@ func TestDBClientIdentityRepoMetadata(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
_, err := r.New("foo", cm)
|
||||
_, err := r.New("foo", cm, false)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ func TestDBClientIdentityRepoNewDuplicate(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
if _, err := r.New("foo", meta1); err != nil {
|
||||
if _, err := r.New("foo", meta1, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
|
@ -237,7 +237,7 @@ func TestDBClientIdentityRepoNewDuplicate(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
if _, err := r.New("foo", meta2); err == nil {
|
||||
if _, err := r.New("foo", meta2, false); err == nil {
|
||||
t.Fatalf("expected non-nil error")
|
||||
}
|
||||
}
|
||||
|
@ -251,7 +251,7 @@ func TestDBClientIdentityRepoAuthenticate(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
cc, err := r.New("baz", cm)
|
||||
cc, err := r.New("baz", cm, false)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
@ -299,7 +299,7 @@ func TestDBClientIdentityAll(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
_, err := r.New("foo", cm)
|
||||
_, err := r.New("foo", cm, false)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
@ -322,7 +322,7 @@ func TestDBClientIdentityAll(t *testing.T) {
|
|||
url.URL{Scheme: "http", Host: "foo.com", Path: "/cb"},
|
||||
},
|
||||
}
|
||||
_, err = r.New("bar", cm)
|
||||
_, err = r.New("bar", cm, false)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
|
@ -12,6 +14,7 @@ import (
|
|||
"github.com/coreos/dex/schema/adminschema"
|
||||
"github.com/coreos/dex/server"
|
||||
"github.com/coreos/dex/user"
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -74,10 +77,10 @@ func (a *adminAPITransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
|||
func makeAdminAPITestFixtures() *adminAPITestFixtures {
|
||||
f := &adminAPITestFixtures{}
|
||||
|
||||
ur, pwr, um := makeUserObjects(adminUsers, adminPasswords)
|
||||
dbMap, ur, pwr, um := makeUserObjects(adminUsers, adminPasswords)
|
||||
f.ur = ur
|
||||
f.pwr = pwr
|
||||
f.adAPI = admin.NewAdminAPI(um, f.ur, f.pwr, "local")
|
||||
f.adAPI = admin.NewAdminAPI(dbMap, um, "local")
|
||||
f.adSrv = server.NewAdminServer(f.adAPI, nil, adminAPITestSecret)
|
||||
f.hSrv = httptest.NewServer(f.adSrv.HTTPHandler())
|
||||
f.hc = &http.Client{
|
||||
|
@ -252,6 +255,52 @@ func TestCreateAdmin(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCreateClient(t *testing.T) {
|
||||
tests := []struct {
|
||||
client oidc.ClientMetadata
|
||||
wantError bool
|
||||
}{
|
||||
{
|
||||
client: oidc.ClientMetadata{},
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
client: oidc.ClientMetadata{
|
||||
RedirectURIs: []url.URL{
|
||||
{Scheme: "https", Host: "auth.example.com", Path: "/"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
err := func() error {
|
||||
f := makeAdminAPITestFixtures()
|
||||
req := &adminschema.ClientCreateRequestClient{}
|
||||
for _, redirectURI := range tt.client.RedirectURIs {
|
||||
req.Redirect_uris = append(req.Redirect_uris, redirectURI.String())
|
||||
}
|
||||
resp, err := f.adClient.Client.Create(&adminschema.ClientCreateRequest{Client: req}).Do()
|
||||
if err != nil {
|
||||
if tt.wantError {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if resp.Client_id == "" {
|
||||
return errors.New("no client id returned")
|
||||
}
|
||||
if resp.Client_secret == "" {
|
||||
return errors.New("no client secret returned")
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
t.Errorf("case %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetState(t *testing.T) {
|
||||
tests := []struct {
|
||||
addUsers []user.User
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/url"
|
||||
|
||||
"github.com/coreos/go-oidc/key"
|
||||
"github.com/go-gorp/gorp"
|
||||
"github.com/jonboulle/clockwork"
|
||||
|
||||
"github.com/coreos/dex/connector"
|
||||
|
@ -45,7 +46,9 @@ func (t *tokenHandlerTransport) RoundTrip(r *http.Request) (*http.Response, erro
|
|||
return &resp, nil
|
||||
}
|
||||
|
||||
func makeUserObjects(users []user.UserWithRemoteIdentities, passwords []user.PasswordInfo) (user.UserRepo, user.PasswordInfoRepo, *manager.UserManager) {
|
||||
// TODO(ericchiang): Replace DbMap with storage interface. See #278
|
||||
|
||||
func makeUserObjects(users []user.UserWithRemoteIdentities, passwords []user.PasswordInfo) (*gorp.DbMap, user.UserRepo, user.PasswordInfoRepo, *manager.UserManager) {
|
||||
dbMap := db.NewMemDB()
|
||||
ur := func() user.UserRepo {
|
||||
repo, err := db.NewUserRepoFromUsers(dbMap, users)
|
||||
|
@ -73,5 +76,5 @@ func makeUserObjects(users []user.UserWithRemoteIdentities, passwords []user.Pas
|
|||
|
||||
um := manager.NewUserManager(ur, pwr, ccr, db.TransactionFactory(dbMap), manager.ManagerOptions{})
|
||||
um.Clock = clock
|
||||
return ur, pwr, um
|
||||
return dbMap, ur, pwr, um
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ var (
|
|||
func makeUserAPITestFixtures() *userAPITestFixtures {
|
||||
f := &userAPITestFixtures{}
|
||||
|
||||
_, _, um := makeUserObjects(userUsers, userPasswords)
|
||||
_, _, _, um := makeUserObjects(userUsers, userPasswords)
|
||||
|
||||
cir := func() client.ClientIdentityRepo {
|
||||
repo, err := db.NewClientIdentityRepoFromClients(db.NewMemDB(), []oidc.ClientIdentity{
|
||||
|
|
|
@ -20,6 +20,35 @@ __Version:__ v1
|
|||
}
|
||||
```
|
||||
|
||||
### ClientCreateRequest
|
||||
|
||||
A request to register a client with dex.
|
||||
|
||||
```
|
||||
{
|
||||
client: {
|
||||
client_name: string // OPTIONAL. Name of the Client to be presented to the End-User. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) .,
|
||||
client_uri: string // OPTIONAL. URL of the home page of the Client. The value of this field MUST point to a valid Web page. If present, the server SHOULD display this URL to the End-User in a followable fashion. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) .,
|
||||
logo_uri: string // OPTIONAL. URL that references a logo for the Client application. If present, the server SHOULD display this image to the End-User during approval. The value of this field MUST point to a valid image file. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) .,
|
||||
redirect_uris: [
|
||||
string
|
||||
]
|
||||
},
|
||||
isAdmin: boolean
|
||||
}
|
||||
```
|
||||
|
||||
### ClientRegistrationResponse
|
||||
|
||||
Upon successful registration, an ID and secret is assigned to the client.
|
||||
|
||||
```
|
||||
{
|
||||
client_id: string,
|
||||
client_secret: string
|
||||
}
|
||||
```
|
||||
|
||||
### State
|
||||
|
||||
|
||||
|
@ -86,6 +115,32 @@ __Version:__ v1
|
|||
| default | Unexpected error | |
|
||||
|
||||
|
||||
### POST /client
|
||||
|
||||
> __Summary__
|
||||
|
||||
> Create Client
|
||||
|
||||
> __Description__
|
||||
|
||||
> Register an OpenID Connect client.
|
||||
|
||||
|
||||
> __Parameters__
|
||||
|
||||
> |Name|Located in|Description|Required|Type|
|
||||
|:-----|:-----|:-----|:-----|:-----|
|
||||
| | body | | Yes | [ClientCreateRequest](#clientcreaterequest) |
|
||||
|
||||
|
||||
> __Responses__
|
||||
|
||||
> |Code|Description|Type|
|
||||
|:-----|:-----|:-----|
|
||||
| 200 | | [ClientRegistrationResponse](#clientregistrationresponse) |
|
||||
| default | Unexpected error | |
|
||||
|
||||
|
||||
### GET /state
|
||||
|
||||
> __Summary__
|
||||
|
|
|
@ -46,6 +46,7 @@ func New(client *http.Client) (*Service, error) {
|
|||
}
|
||||
s := &Service{client: client, BasePath: basePath}
|
||||
s.Admin = NewAdminService(s)
|
||||
s.Client = NewClientService(s)
|
||||
s.State = NewStateService(s)
|
||||
return s, nil
|
||||
}
|
||||
|
@ -56,6 +57,8 @@ type Service struct {
|
|||
|
||||
Admin *AdminService
|
||||
|
||||
Client *ClientService
|
||||
|
||||
State *StateService
|
||||
}
|
||||
|
||||
|
@ -68,6 +71,15 @@ type AdminService struct {
|
|||
s *Service
|
||||
}
|
||||
|
||||
func NewClientService(s *Service) *ClientService {
|
||||
rs := &ClientService{s: s}
|
||||
return rs
|
||||
}
|
||||
|
||||
type ClientService struct {
|
||||
s *Service
|
||||
}
|
||||
|
||||
func NewStateService(s *Service) *StateService {
|
||||
rs := &StateService{s: s}
|
||||
return rs
|
||||
|
@ -85,6 +97,51 @@ type Admin struct {
|
|||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type ClientCreateRequest struct {
|
||||
Client *ClientCreateRequestClient `json:"client,omitempty"`
|
||||
|
||||
IsAdmin bool `json:"isAdmin,omitempty"`
|
||||
}
|
||||
|
||||
type ClientCreateRequestClient struct {
|
||||
// Client_name: OPTIONAL. Name of the Client to be presented to the
|
||||
// End-User. If desired, representation of this Claim in different
|
||||
// languages and scripts is represented as described in Section 2.1 (
|
||||
// Metadata Languages and Scripts ) .
|
||||
Client_name string `json:"client_name,omitempty"`
|
||||
|
||||
// Client_uri: OPTIONAL. URL of the home page of the Client. The value
|
||||
// of this field MUST point to a valid Web page. If present, the server
|
||||
// SHOULD display this URL to the End-User in a followable fashion. If
|
||||
// desired, representation of this Claim in different languages and
|
||||
// scripts is represented as described in Section 2.1 ( Metadata
|
||||
// Languages and Scripts ) .
|
||||
Client_uri string `json:"client_uri,omitempty"`
|
||||
|
||||
// Logo_uri: OPTIONAL. URL that references a logo for the Client
|
||||
// application. If present, the server SHOULD display this image to the
|
||||
// End-User during approval. The value of this field MUST point to a
|
||||
// valid image file. If desired, representation of this Claim in
|
||||
// different languages and scripts is represented as described in
|
||||
// Section 2.1 ( Metadata Languages and Scripts ) .
|
||||
Logo_uri string `json:"logo_uri,omitempty"`
|
||||
|
||||
// Redirect_uris: REQUIRED. Array of Redirection URI values used by the
|
||||
// Client. One of these registered Redirection URI values MUST exactly
|
||||
// match the redirect_uri parameter value used in each Authorization
|
||||
// Request, with the matching performed as described in Section 6.2.1 of
|
||||
// [RFC3986] ( Berners-Lee, T., Fielding, R., and L. Masinter,
|
||||
// “Uniform Resource Identifier (URI): Generic Syntax,” January
|
||||
// 2005. ) (Simple String Comparison).
|
||||
Redirect_uris []string `json:"redirect_uris,omitempty"`
|
||||
}
|
||||
|
||||
type ClientRegistrationResponse struct {
|
||||
Client_id string `json:"client_id,omitempty"`
|
||||
|
||||
Client_secret string `json:"client_secret,omitempty"`
|
||||
}
|
||||
|
||||
type State struct {
|
||||
AdminUserCreated bool `json:"AdminUserCreated,omitempty"`
|
||||
}
|
||||
|
@ -230,6 +287,75 @@ func (c *AdminGetCall) Do() (*Admin, error) {
|
|||
|
||||
}
|
||||
|
||||
// method id "dex.admin.Client.Create":
|
||||
|
||||
type ClientCreateCall struct {
|
||||
s *Service
|
||||
clientcreaterequest *ClientCreateRequest
|
||||
opt_ map[string]interface{}
|
||||
}
|
||||
|
||||
// Create: Register an OpenID Connect client.
|
||||
func (r *ClientService) Create(clientcreaterequest *ClientCreateRequest) *ClientCreateCall {
|
||||
c := &ClientCreateCall{s: r.s, opt_: make(map[string]interface{})}
|
||||
c.clientcreaterequest = clientcreaterequest
|
||||
return c
|
||||
}
|
||||
|
||||
// Fields allows partial responses to be retrieved.
|
||||
// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
|
||||
// for more information.
|
||||
func (c *ClientCreateCall) Fields(s ...googleapi.Field) *ClientCreateCall {
|
||||
c.opt_["fields"] = googleapi.CombineFields(s)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ClientCreateCall) Do() (*ClientRegistrationResponse, error) {
|
||||
var body io.Reader = nil
|
||||
body, err := googleapi.WithoutDataWrapper.JSONReader(c.clientcreaterequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctype := "application/json"
|
||||
params := make(url.Values)
|
||||
params.Set("alt", "json")
|
||||
if v, ok := c.opt_["fields"]; ok {
|
||||
params.Set("fields", fmt.Sprintf("%v", v))
|
||||
}
|
||||
urls := googleapi.ResolveRelative(c.s.BasePath, "client")
|
||||
urls += "?" + params.Encode()
|
||||
req, _ := http.NewRequest("POST", urls, body)
|
||||
googleapi.SetOpaque(req.URL)
|
||||
req.Header.Set("Content-Type", ctype)
|
||||
req.Header.Set("User-Agent", "google-api-go-client/0.5")
|
||||
res, err := c.s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer googleapi.CloseBody(res)
|
||||
if err := googleapi.CheckResponse(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ret *ClientRegistrationResponse
|
||||
if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
// {
|
||||
// "description": "Register an OpenID Connect client.",
|
||||
// "httpMethod": "POST",
|
||||
// "id": "dex.admin.Client.Create",
|
||||
// "path": "client",
|
||||
// "request": {
|
||||
// "$ref": "ClientCreateRequest"
|
||||
// },
|
||||
// "response": {
|
||||
// "$ref": "ClientRegistrationResponse"
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// method id "dex.admin.State.Get":
|
||||
|
||||
type StateGetCall struct {
|
||||
|
|
|
@ -50,6 +50,53 @@ const DiscoveryJSON = `{
|
|||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ClientCreateRequest": {
|
||||
"id": "ClientCreateRequest",
|
||||
"type": "object",
|
||||
"description": "A request to register a client with dex.",
|
||||
"properties": {
|
||||
"isAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"client": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"redirect_uris": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "REQUIRED. Array of Redirection URI values used by the Client. One of these registered Redirection URI values MUST exactly match the redirect_uri parameter value used in each Authorization Request, with the matching performed as described in Section 6.2.1 of [RFC3986] ( Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005. ) (Simple String Comparison)."
|
||||
},
|
||||
"client_name": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL. Name of the Client to be presented to the End-User. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
|
||||
},
|
||||
"logo_uri": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL. URL that references a logo for the Client application. If present, the server SHOULD display this image to the End-User during approval. The value of this field MUST point to a valid image file. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
|
||||
},
|
||||
"client_uri": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL. URL of the home page of the Client. The value of this field MUST point to a valid Web page. If present, the server SHOULD display this URL to the End-User in a followable fashion. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ClientRegistrationResponse": {
|
||||
"id": "ClientRegistrationResponse",
|
||||
"type": "object",
|
||||
"description": "Upon successful registration, an ID and secret is assigned to the client.",
|
||||
"properties": {
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"client_secret": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
|
@ -101,6 +148,22 @@ const DiscoveryJSON = `{
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Client": {
|
||||
"methods": {
|
||||
"Create": {
|
||||
"id": "dex.admin.Client.Create",
|
||||
"description": "Register an OpenID Connect client.",
|
||||
"httpMethod": "POST",
|
||||
"path": "client",
|
||||
"request": {
|
||||
"$ref": "ClientCreateRequest"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "ClientRegistrationResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,53 @@
|
|||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ClientCreateRequest": {
|
||||
"id": "ClientCreateRequest",
|
||||
"type": "object",
|
||||
"description": "A request to register a client with dex.",
|
||||
"properties": {
|
||||
"isAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"client": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"redirect_uris": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "REQUIRED. Array of Redirection URI values used by the Client. One of these registered Redirection URI values MUST exactly match the redirect_uri parameter value used in each Authorization Request, with the matching performed as described in Section 6.2.1 of [RFC3986] ( Berners-Lee, T., Fielding, R., and L. Masinter, “Uniform Resource Identifier (URI): Generic Syntax,” January 2005. ) (Simple String Comparison)."
|
||||
},
|
||||
"client_name": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL. Name of the Client to be presented to the End-User. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
|
||||
},
|
||||
"logo_uri": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL. URL that references a logo for the Client application. If present, the server SHOULD display this image to the End-User during approval. The value of this field MUST point to a valid image file. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
|
||||
},
|
||||
"client_uri": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL. URL of the home page of the Client. The value of this field MUST point to a valid Web page. If present, the server SHOULD display this URL to the End-User in a followable fashion. If desired, representation of this Claim in different languages and scripts is represented as described in Section 2.1 ( Metadata Languages and Scripts ) ."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ClientRegistrationResponse": {
|
||||
"id": "ClientRegistrationResponse",
|
||||
"type": "object",
|
||||
"description": "Upon successful registration, an ID and secret is assigned to the client.",
|
||||
"properties": {
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"client_secret": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
|
@ -95,6 +142,22 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Client": {
|
||||
"methods": {
|
||||
"Create": {
|
||||
"id": "dex.admin.Client.Create",
|
||||
"description": "Register an OpenID Connect client.",
|
||||
"httpMethod": "POST",
|
||||
"path": "client",
|
||||
"request": {
|
||||
"$ref": "ClientCreateRequest"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "ClientRegistrationResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ var (
|
|||
AdminGetEndpoint = addBasePath("/admin/:id")
|
||||
AdminCreateEndpoint = addBasePath("/admin")
|
||||
AdminGetStateEndpoint = addBasePath("/state")
|
||||
AdminCreateClientEndpoint = addBasePath("/client")
|
||||
)
|
||||
|
||||
// AdminServer serves the admin API.
|
||||
|
@ -49,6 +50,7 @@ func (s *AdminServer) HTTPHandler() http.Handler {
|
|||
r.GET(AdminGetEndpoint, s.getAdmin)
|
||||
r.POST(AdminCreateEndpoint, s.createAdmin)
|
||||
r.GET(AdminGetStateEndpoint, s.getState)
|
||||
r.POST(AdminCreateClientEndpoint, s.createClient)
|
||||
r.Handler("GET", httpPathHealth, s.checker)
|
||||
r.HandlerFunc("GET", httpPathDebugVars, health.ExpvarHandler)
|
||||
|
||||
|
@ -113,6 +115,21 @@ func (s *AdminServer) getState(w http.ResponseWriter, r *http.Request, ps httpro
|
|||
writeResponseWithBody(w, http.StatusOK, state)
|
||||
}
|
||||
|
||||
func (s *AdminServer) createClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
var req admin.ClientRegistrationRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeInvalidRequest(w, "cannot parse JSON body")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := s.adminAPI.CreateClient(req)
|
||||
if err != nil {
|
||||
s.writeError(w, err)
|
||||
return
|
||||
}
|
||||
writeResponseWithBody(w, http.StatusOK, &resp)
|
||||
}
|
||||
|
||||
func (s *AdminServer) writeError(w http.ResponseWriter, err error) {
|
||||
log.Errorf("Error calling admin API: %v: ", err)
|
||||
if adminErr, ok := err.(admin.Error); ok {
|
||||
|
|
|
@ -43,7 +43,7 @@ func (s *Server) handleClientRegistrationRequest(r *http.Request) (*oidc.ClientR
|
|||
return nil, newAPIError(oauth2.ErrorServerError, "unable to save client metadata")
|
||||
}
|
||||
|
||||
creds, err := s.ClientIdentityRepo.New(id, clientMetadata)
|
||||
creds, err := s.ClientIdentityRepo.New(id, clientMetadata, false)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create new client identity: %v", err)
|
||||
return nil, newAPIError(oauth2.ErrorServerError, "unable to save client metadata")
|
||||
|
|
|
@ -96,7 +96,7 @@ func (c *clientResource) create(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
creds, err := c.repo.New(clientID, ci.Metadata)
|
||||
creds, err := c.repo.New(clientID, ci.Metadata, false)
|
||||
if err != nil {
|
||||
log.Errorf("Failed creating client: %v", err)
|
||||
writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "unable to create client"))
|
||||
|
|
Reference in a new issue