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 all 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.
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
19 changes: 18 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.
OrganizationUsage *string `json:"organization_usage,omitempty"`
}

// ClientGrantList is a list of ClientGrants.
Expand Down Expand Up @@ -87,3 +95,12 @@ 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.
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.

23 changes: 23 additions & 0 deletions management/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,26 @@ 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.
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.
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 removes a client grant from an organization.
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
Loading