Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for using Organizations with Client Grants #309

Merged
merged 5 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions authentication/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ func (o *OAuth) LoginWithClientCredentials(ctx context.Context, body oauth.Login
"audience": []string{body.Audience},
}

if body.Organization != "" {
data.Set("organization", body.Organization)
}

err = o.addClientAuthentication(body.ClientAuthentication, data, true)

if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions authentication/oauth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ type LoginWithClientCredentialsRequest struct {
Audience string
// Extra parameters to be merged into the request body. Values set here will override any existing values.
ExtraParameters map[string]string
// And organization name or ID. When included the access token will include the org_id or org_name claim.
ewanharris marked this conversation as resolved.
Show resolved Hide resolved
Organization string
}

// RefreshTokenRequest defines the request body for logging in with Authorization Code grant.
Expand Down
16 changes: 16 additions & 0 deletions authentication/oauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,22 @@ func TestLoginWithClientCredentials(t *testing.T) {

assert.ErrorContains(t, err, "Unsupported client assertion algorithm \"invalid-alg\" provided")
})

t.Run("Should support passing an organization", func(t *testing.T) {
configureHTTPTestRecordings(t)

tokenSet, err := authAPI.OAuth.LoginWithClientCredentials(context.Background(), oauth.LoginWithClientCredentialsRequest{
ClientAuthentication: oauth.ClientAuthentication{
ClientSecret: clientSecret,
},
Audience: "my-api",
Organization: "org_test",
}, oauth.IDTokenValidationOptions{})

assert.NoError(t, err)
assert.NotEmpty(t, tokenSet.AccessToken)
assert.Equal(t, "Bearer", tokenSet.TokenType)
})
}

func TestRefreshToken(t *testing.T) {
Expand Down
21 changes: 20 additions & 1 deletion management/client_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@ type ClientGrant struct {
// The audience.
Audience *string `json:"audience,omitempty"`

Scope []string `json:"scope"`
Scope []string `json:"scope,omitempty"`

// If enabled, any organization can be used with this grant.
// If disabled (default), the grant must be explicitly assigned to the desired organizations.
AllowAnyOrganization *bool `json:"allow_any_organization,omitempty"`

// Defines whether organizations can be used with client credentials exchanges for this grant.
// Can be one of deny, allow, or require. Defaults to deny when not defined.
ewanharris marked this conversation as resolved.
Show resolved Hide resolved
OrganizationUsage *string `json:"organization_usage,omitempty"`
}

// ClientGrantList is a list of ClientGrants.
Expand Down Expand Up @@ -87,3 +95,14 @@ func (m *ClientGrantManager) List(ctx context.Context, opts ...RequestOption) (g
err = m.management.Request(ctx, "GET", m.management.URI("client-grants"), &gs, applyListDefaults(opts))
return
}

// Organizations lists client grants associated to an organization.
//
// This method forces the `include_totals=true` and defaults to `per_page=50` if
// not provided.
//
// See: https://auth0.com/docs/api/management/v2/client-grants/get-organizations
func (m *ClientGrantManager) Organizations(ctx context.Context, id string, opts ...RequestOption) (o *OrganizationList, err error) {
err = m.management.Request(ctx, "GET", m.management.URI("client-grants", id, "organizations"), &o, applyListDefaults(opts))
return
}
31 changes: 26 additions & 5 deletions management/client_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/auth0/go-auth0"
)

func TestClientGrantManager_Create(t *testing.T) {
Expand All @@ -31,7 +33,7 @@ func TestClientGrantManager_Create(t *testing.T) {
func TestClientGrantManager_Read(t *testing.T) {
configureHTTPTestRecordings(t)

expectedClientGrant := givenAClientGrant(t)
expectedClientGrant := givenAClientGrant(t, false)

actualClientGrant, err := api.ClientGrant.Read(context.Background(), expectedClientGrant.GetID())

Expand All @@ -44,7 +46,7 @@ func TestClientGrantManager_Read(t *testing.T) {
func TestClientGrantManager_Update(t *testing.T) {
configureHTTPTestRecordings(t)

expectedClientGrant := givenAClientGrant(t)
expectedClientGrant := givenAClientGrant(t, false)

clientGrantID := expectedClientGrant.GetID()

Expand All @@ -64,7 +66,7 @@ func TestClientGrantManager_Update(t *testing.T) {
func TestClientGrantManager_Delete(t *testing.T) {
configureHTTPTestRecordings(t)

expectedClientGrant := givenAClientGrant(t)
expectedClientGrant := givenAClientGrant(t, false)

err := api.ClientGrant.Delete(context.Background(), expectedClientGrant.GetID())
assert.NoError(t, err)
Expand All @@ -77,7 +79,7 @@ func TestClientGrantManager_Delete(t *testing.T) {
func TestClientGrantManager_List(t *testing.T) {
configureHTTPTestRecordings(t)

expectedClientGrant := givenAClientGrant(t)
expectedClientGrant := givenAClientGrant(t, false)

clientGrantList, err := api.ClientGrant.List(
context.Background(),
Expand All @@ -88,7 +90,21 @@ func TestClientGrantManager_List(t *testing.T) {
assert.Equal(t, len(clientGrantList.ClientGrants), 1)
}

func givenAClientGrant(t *testing.T) (clientGrant *ClientGrant) {
func TestClientGrantManager_Organizations(t *testing.T) {
configureHTTPTestRecordings(t)

org := givenAnOrganization(t)
clientGrant := givenAClientGrant(t, true)

err := api.Organization.AssociateClientGrant(context.Background(), org.GetID(), clientGrant.GetID())
require.NoError(t, err)

associatedOrg, err := api.ClientGrant.Organizations(context.Background(), clientGrant.GetID())
require.NoError(t, err)
assert.Equal(t, org.GetID(), associatedOrg.Organizations[0].GetID())
}

func givenAClientGrant(t *testing.T, allowOrganizations bool) (clientGrant *ClientGrant) {
t.Helper()

client := givenAClient(t)
Expand All @@ -100,6 +116,11 @@ func givenAClientGrant(t *testing.T) (clientGrant *ClientGrant) {
Scope: []string{"create:resource"},
}

if allowOrganizations {
clientGrant.AllowAnyOrganization = auth0.Bool(true)
clientGrant.OrganizationUsage = auth0.String("allow")
}

err := api.ClientGrant.Create(context.Background(), clientGrant)
require.NoError(t, err)

Expand Down
16 changes: 16 additions & 0 deletions management/management.gen.go

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

20 changes: 20 additions & 0 deletions management/management.gen_test.go

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

29 changes: 29 additions & 0 deletions management/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,32 @@ func (m *OrganizationManager) DeleteMemberRoles(ctx context.Context, id string,
err = m.management.Request(ctx, "DELETE", m.management.URI("organizations", id, "members", memberID, "roles"), &body, opts...)
return
}

// ClientGrants retrieves the client grants assigned to an organization.
//
// See: https://auth0.com/docs/api/management/v2/organizations/get-client-grants
func (m *OrganizationManager) ClientGrants(ctx context.Context, id string, opts ...RequestOption) (g *ClientGrantList, err error) {
err = m.management.Request(ctx, "GET", m.management.URI("organizations", id, "client-grants"), &g, applyListDefaults(opts))
return
}

// AssociateClientGrant assigns a client grant to an organization.
//
// See: https://auth0.com/docs/api/management/v2/organizations/post-client-grants
func (m *OrganizationManager) AssociateClientGrant(ctx context.Context, id string, grantID string, opts ...RequestOption) (err error) {
body := struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to reach out to see whether this body could change over time as that would impact whether we just accept a grantID (which would mean we can't really add to this in future) or the body (which would allow parameters to be added in future)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this should be safe, also removed links for now as they'll 404. Will update in future

GrantID string `json:"grant_id"`
}{
GrantID: grantID,
}
err = m.management.Request(ctx, "POST", m.management.URI("organizations", id, "client-grants"), &body, applyListDefaults(opts))
return
}

// RemoveClientGrant assigns a client grant to an organization.
ewanharris marked this conversation as resolved.
Show resolved Hide resolved
//
// See: https://auth0.com/docs/api/management/v2/organizations/delete-client-grants-by-grant-id
func (m *OrganizationManager) RemoveClientGrant(ctx context.Context, id string, grantID string, opts ...RequestOption) (err error) {
err = m.management.Request(ctx, "DELETE", m.management.URI("organizations", id, "client-grants", grantID), nil, applyListDefaults(opts))
return
}
22 changes: 22 additions & 0 deletions management/organization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,28 @@ func TestOrganizationManager_MemberRoles(t *testing.T) {
assert.Len(t, roles.Roles, 0)
}

func TestOrganizationManager_ClientGrants(t *testing.T) {
configureHTTPTestRecordings(t)

org := givenAnOrganization(t)
clientGrant := givenAClientGrant(t, true)

err := api.Organization.AssociateClientGrant(context.Background(), org.GetID(), clientGrant.GetID())
require.NoError(t, err)

associatedGrants, err := api.Organization.ClientGrants(context.Background(), org.GetID())
require.NoError(t, err)
assert.Len(t, associatedGrants.ClientGrants, 1)
assert.Equal(t, clientGrant.GetID(), associatedGrants.ClientGrants[0].GetID())

err = api.Organization.RemoveClientGrant(context.Background(), org.GetID(), clientGrant.GetID())
require.NoError(t, err)

associatedGrants, err = api.Organization.ClientGrants(context.Background(), org.GetID())
require.NoError(t, err)
assert.Len(t, associatedGrants.ClientGrants, 0)
}

func givenAnOrganization(t *testing.T) *Organization {
org := &Organization{
Name: auth0.String(fmt.Sprintf("test-organization%v", rand.Intn(999))),
Expand Down
Loading