Skip to content

Commit

Permalink
feat: add include_credential query param to /admin/identities lis…
Browse files Browse the repository at this point in the history
…t call (#3343)
  • Loading branch information
borisroman authored Feb 20, 2024
1 parent f47675b commit d94530a
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 3 deletions.
32 changes: 30 additions & 2 deletions identity/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,15 @@ type listIdentitiesParameters struct {
// in: query
CredentialsIdentifierSimilar string `json:"preview_credentials_identifier_similar"`

// Include Credentials in Response
//
// Include any credential, for example `password` or `oidc`, in the response. When set to `oidc`, This will return
// the initial OAuth 2.0 Access Token, OAuth 2.0 Refresh Token and the OpenID Connect ID Token if available.
//
// required: false
// in: query
DeclassifyCredentials []string `json:"include_credential"`

crdbx.ConsistencyRequestParameters
}

Expand All @@ -183,6 +192,18 @@ type listIdentitiesParameters struct {
// 200: listIdentities
// default: errorGeneric
func (h *Handler) list(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
includeCredentials := r.URL.Query()["include_credential"]
var declassify []CredentialsType
for _, v := range includeCredentials {
tc, ok := ParseCredentialsType(v)
if ok {
declassify = append(declassify, tc)
} else {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Invalid value `%s` for parameter `include_credential`.", declassify)))
return
}
}

var (
err error
params = ListIdentityParameters{
Expand All @@ -191,13 +212,14 @@ func (h *Handler) list(w http.ResponseWriter, r *http.Request, _ httprouter.Para
CredentialsIdentifier: r.URL.Query().Get("credentials_identifier"),
CredentialsIdentifierSimilar: r.URL.Query().Get("preview_credentials_identifier_similar"),
ConsistencyLevel: crdbx.ConsistencyLevelFromRequest(r),
DeclassifyCredentials: declassify,
}
)
if params.CredentialsIdentifier != "" && params.CredentialsIdentifierSimilar != "" {
h.r.Writer().WriteError(w, r, herodot.ErrBadRequest.WithReason("Cannot pass both credentials_identifier and preview_credentials_identifier_similar."))
return
}
if params.CredentialsIdentifier != "" || params.CredentialsIdentifierSimilar != "" {
if params.CredentialsIdentifier != "" || params.CredentialsIdentifierSimilar != "" || len(params.DeclassifyCredentials) > 0 {
params.Expand = ExpandEverything
}
params.KeySetPagination, params.PagePagination, err = x.ParseKeysetOrPagePagination(r)
Expand Down Expand Up @@ -231,7 +253,13 @@ func (h *Handler) list(w http.ResponseWriter, r *http.Request, _ httprouter.Para
// Identities using the marshaler for including metadata_admin
isam := make([]WithCredentialsMetadataAndAdminMetadataInJSON, len(is))
for i, identity := range is {
isam[i] = WithCredentialsMetadataAndAdminMetadataInJSON(identity)
emit, err := identity.WithDeclassifiedCredentials(r.Context(), h.r, params.DeclassifyCredentials)
if err != nil {
h.r.Writer().WriteError(w, r, err)
return
}

isam[i] = WithCredentialsMetadataAndAdminMetadataInJSON(*emit)
}

h.r.Writer().Write(w, r, isam)
Expand Down
23 changes: 22 additions & 1 deletion identity/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,7 @@ func TestHandler(t *testing.T) {
})

t.Run("case=should list all identities", func(t *testing.T) {
for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} {
for name, ts := range map[string]*httptest.Server{"admin": adminTS} {
t.Run("endpoint="+name, func(t *testing.T) {
res := get(t, ts, "/identities", http.StatusOK)
assert.False(t, res.Get("0.credentials").Exists(), "credentials config should be omitted: %s", res.Raw)
Expand All @@ -1313,6 +1313,27 @@ func TestHandler(t *testing.T) {
}
})

t.Run("case=should list all identities with credentials", func(t *testing.T) {
for name, ts := range map[string]*httptest.Server{"admin": adminTS} {
t.Run("endpoint="+name, func(t *testing.T) {
res := get(t, ts, "/identities?include_credential=totp", http.StatusOK)
assert.True(t, res.Get("0.credentials").Exists(), "credentials config should be included: %s", res.Raw)
assert.True(t, res.Get("0.metadata_public").Exists(), "metadata_public config should be included: %s", res.Raw)
assert.True(t, res.Get("0.metadata_admin").Exists(), "metadata_admin config should be included: %s", res.Raw)
assert.EqualValues(t, "baz", res.Get(`#(traits.bar=="baz").traits.bar`).String(), "%s", res.Raw)
})
}
})

t.Run("case=should not be able to list all identities with credentials due to wrong credentials type", func(t *testing.T) {
for name, ts := range map[string]*httptest.Server{"admin": adminTS} {
t.Run("endpoint="+name, func(t *testing.T) {
res := get(t, ts, "/identities?include_credential=XYZ", http.StatusBadRequest)
assert.Contains(t, res.Get("error.message").String(), "The request was malformed or contained invalid parameters", "%s", res.Raw)
})
}
})

t.Run("case=should list all identities with eventual consistency", func(t *testing.T) {
for name, ts := range map[string]*httptest.Server{"public": publicTS, "admin": adminTS} {
t.Run("endpoint="+name, func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions identity/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type (
IdsFilter []string
CredentialsIdentifier string
CredentialsIdentifierSimilar string
DeclassifyCredentials []CredentialsType
KeySetPagination []keysetpagination.Option
// DEPRECATED
PagePagination *x.Page
Expand Down
16 changes: 16 additions & 0 deletions internal/client-go/api_identity.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions internal/httpclient/api_identity.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions spec/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -3628,6 +3628,17 @@
"schema": {
"type": "string"
}
},
{
"description": "Include Credentials in Response\n\nInclude any credential, for example `password` or `oidc`, in the response. When set to `oidc`, This will return\nthe initial OAuth 2.0 Access Token, OAuth 2.0 Refresh Token and the OpenID Connect ID Token if available.",
"in": "query",
"name": "include_credential",
"schema": {
"items": {
"type": "string"
},
"type": "array"
}
}
],
"responses": {
Expand Down
9 changes: 9 additions & 0 deletions spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,15 @@
"description": "This is an EXPERIMENTAL parameter that WILL CHANGE. Do NOT rely on consistent, deterministic behavior.\nTHIS PARAMETER WILL BE REMOVED IN AN UPCOMING RELEASE WITHOUT ANY MIGRATION PATH.\n\nCredentialsIdentifierSimilar is the (partial) identifier (username, email) of the credentials to look up using similarity search.\nOnly one of CredentialsIdentifier and CredentialsIdentifierSimilar can be used.",
"name": "preview_credentials_identifier_similar",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"description": "Include Credentials in Response\n\nInclude any credential, for example `password` or `oidc`, in the response. When set to `oidc`, This will return\nthe initial OAuth 2.0 Access Token, OAuth 2.0 Refresh Token and the OpenID Connect ID Token if available.",
"name": "include_credential",
"in": "query"
}
],
"responses": {
Expand Down

0 comments on commit d94530a

Please sign in to comment.