Skip to content

Commit

Permalink
feat: add support for trust grants that can issue tokens for any subj…
Browse files Browse the repository at this point in the history
…ect (#3012)

Previously, a trust relationship had to be setup for every subject
before the issuer could sign a JWT token for it. This change will allow
setting up token services that can issue tokens with any value in the
subject field.

Closes #2930

Co-authored-by: aeneasr <[email protected]>
  • Loading branch information
jagobagascon and aeneasr authored Apr 17, 2022
1 parent 05286df commit a3c4304
Show file tree
Hide file tree
Showing 27 changed files with 577 additions and 119 deletions.
58 changes: 55 additions & 3 deletions cypress/integration/admin/grant_jwtbearer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ dayjs.extend(isBetween)

describe('The JWT-Bearer Grants Admin Interface', () => {
let d = dayjs().utc().add(1, 'year').set('millisecond', 0)
const newGrant = (issuer = 'token-service', subject = '[email protected]') => ({
issuer,
subject,
const newGrant = () => ({
issuer: 'token-service',
subject: '[email protected]',
expires_at: d.toISOString(),
scope: ['openid', 'offline'],
jwk: {
Expand Down Expand Up @@ -119,4 +119,56 @@ describe('The JWT-Bearer Grants Admin Interface', () => {
expect(response.status).to.equal(400)
})
})

it('should fail, because trying to create grant with no subject and no allow_any_subject flag', () => {
const grant = newGrant()
delete grant.subject
delete grant.allow_any_subject
cy.request({
method: 'POST',
url: Cypress.env('admin_url') + '/trust/grants/jwt-bearer/issuers',
failOnStatusCode: false,
body: JSON.stringify(grant)
}).then((response) => {
expect(response.status).to.equal(400)
})
})

it('should return newly created jwt-bearer grant when issuer is allowed to authorize any subject', () => {
const grant = newGrant()
delete grant.subject
grant.allow_any_subject = true
const start = dayjs().subtract(1, 'minutes')
const end = dayjs().add(1, 'minutes')
cy.request(
'POST',
Cypress.env('admin_url') + '/trust/grants/jwt-bearer/issuers',
JSON.stringify(grant)
).then((response) => {
const createdAt = dayjs(response.body.created_at)
const expiresAt = dayjs(response.body.expires_at)
const grantID = response.body.id

expect(response.body.allow_any_subject).to.equal(grant.allow_any_subject)
expect(response.body.issuer).to.equal(grant.issuer)
expect(createdAt.isBetween(start, end)).to.true
expect(expiresAt.isSame(grant.expires_at)).to.true
expect(response.body.scope).to.deep.equal(grant.scope)
expect(response.body.public_key.set).to.equal(grant.issuer)
expect(response.body.public_key.kid).to.equal(grant.jwk.kid)

cy.request(
'GET',
Cypress.env('admin_url') + '/trust/grants/jwt-bearer/issuers/' + grantID
).then((response) => {
expect(response.body.allow_any_subject).to.equal(
grant.allow_any_subject
)
expect(response.body.issuer).to.equal(grant.issuer)
expect(response.body.scope).to.deep.equal(grant.scope)
expect(response.body.public_key.set).to.equal(grant.issuer)
expect(response.body.public_key.kid).to.equal(grant.jwk.kid)
})
})
})
})
39 changes: 39 additions & 0 deletions cypress/integration/oauth2/grant_jwtbearer.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ describe('The OAuth 2.0 JWT Bearer (RFC 7523) Grant', function () {
const gr = (subject) => ({
issuer: prng(),
subject: subject,
allow_any_subject: subject === '',
scope: ['foo', 'openid', 'offline_access'],
jwk: testPublicJwk,
expires_at: dayjs().utc().add(1, 'year').set('millisecond', 0).toISOString()
Expand Down Expand Up @@ -344,6 +345,44 @@ describe('The OAuth 2.0 JWT Bearer (RFC 7523) Grant', function () {
})
})

it('should return an Access Token when given client credentials and a JWT assertion with any subject', function () {
const client = nc()
createClient(client)

const grant = gr('') // allow any subject
createGrant(grant)

const assertion = jwt.sign(
jwtAssertion(grant, { sub: 'any-subject-is-valid' }),
testPrivatePem,
{
algorithm: 'RS256'
}
)

cy.request({
method: 'POST',
url: tokenUrl,
form: true,
body: {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: assertion,
scope: client.scope,
client_secret: client.client_secret,
client_id: client.client_id
}
})
.its('body')
.then((body) => {
const { access_token, expires_in, scope, token_type } = body

expect(access_token).to.not.be.empty
expect(expires_in).to.not.be.undefined
expect(scope).to.not.be.empty
expect(token_type).to.not.be.empty
})
})

it('should return an Error (400) when given client credentials and a JWT assertion with an invalid issuer', function () {
const client = nc()
createClient(client)
Expand Down
10 changes: 9 additions & 1 deletion internal/httpclient-next/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3541,6 +3541,10 @@ components:
type: object
trustJwtGrantIssuerBody:
properties:
allow_any_subject:
description: The "allow_any_subject" indicates that the issuer is allowed
to have any principal as the subject of the JWT.
type: boolean
expires_at:
description: The "expires_at" indicates, when grant will expire, so we will
reject assertion from "issuer" targeting "subject".
Expand Down Expand Up @@ -3572,7 +3576,6 @@ components:
- issuer
- jwk
- scope
- subject
type: object
trustedJsonWebKey:
example:
Expand Down Expand Up @@ -3602,8 +3605,13 @@ components:
- offline
created_at: 2000-01-23T04:56:07.000+00:00
id: 9edc811f-4e28-453c-9b46-4de65f00217f
allow_any_subject: true
issuer: https://jwt-idp.example.com
properties:
allow_any_subject:
description: The "allow_any_subject" indicates that the issuer is allowed
to have any principal as the subject of the JWT.
type: boolean
created_at:
description: The "created_at" indicates, when grant was created.
format: date-time
Expand Down
2 changes: 1 addition & 1 deletion internal/httpclient-next/docs/AdminApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -1971,7 +1971,7 @@ import (
)

func main() {
trustJwtGrantIssuerBody := *openapiclient.NewTrustJwtGrantIssuerBody(time.Now(), "https://jwt-idp.example.com", *openapiclient.NewJSONWebKey("RS256", "1603dfe0af8f4596", "RSA", "sig"), []string{"Scope_example"}, "[email protected]") // TrustJwtGrantIssuerBody | (optional)
trustJwtGrantIssuerBody := *openapiclient.NewTrustJwtGrantIssuerBody(time.Now(), "https://jwt-idp.example.com", *openapiclient.NewJSONWebKey("RS256", "1603dfe0af8f4596", "RSA", "sig"), []string{"Scope_example"}) // TrustJwtGrantIssuerBody | (optional)

configuration := openapiclient.NewConfiguration()
apiClient := openapiclient.NewAPIClient(configuration)
Expand Down
35 changes: 33 additions & 2 deletions internal/httpclient-next/docs/TrustJwtGrantIssuerBody.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@

Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**AllowAnySubject** | Pointer to **bool** | The \&quot;allow_any_subject\&quot; indicates that the issuer is allowed to have any principal as the subject of the JWT. | [optional]
**ExpiresAt** | **time.Time** | The \&quot;expires_at\&quot; indicates, when grant will expire, so we will reject assertion from \&quot;issuer\&quot; targeting \&quot;subject\&quot;. |
**Issuer** | **string** | The \&quot;issuer\&quot; identifies the principal that issued the JWT assertion (same as \&quot;iss\&quot; claim in JWT). |
**Jwk** | [**JSONWebKey**](JSONWebKey.md) | |
**Scope** | **[]string** | The \&quot;scope\&quot; contains list of scope values (as described in Section 3.3 of OAuth 2.0 [RFC6749]) |
**Subject** | **string** | The \&quot;subject\&quot; identifies the principal that is the subject of the JWT. |
**Subject** | Pointer to **string** | The \&quot;subject\&quot; identifies the principal that is the subject of the JWT. | [optional]

## Methods

### NewTrustJwtGrantIssuerBody

`func NewTrustJwtGrantIssuerBody(expiresAt time.Time, issuer string, jwk JSONWebKey, scope []string, subject string, ) *TrustJwtGrantIssuerBody`
`func NewTrustJwtGrantIssuerBody(expiresAt time.Time, issuer string, jwk JSONWebKey, scope []string, ) *TrustJwtGrantIssuerBody`

NewTrustJwtGrantIssuerBody instantiates a new TrustJwtGrantIssuerBody object
This constructor will assign default values to properties that have it defined,
Expand All @@ -29,6 +30,31 @@ NewTrustJwtGrantIssuerBodyWithDefaults instantiates a new TrustJwtGrantIssuerBod
This constructor will only assign default values to properties that have it defined,
but it doesn't guarantee that properties required by API are set

### GetAllowAnySubject

`func (o *TrustJwtGrantIssuerBody) GetAllowAnySubject() bool`

GetAllowAnySubject returns the AllowAnySubject field if non-nil, zero value otherwise.

### GetAllowAnySubjectOk

`func (o *TrustJwtGrantIssuerBody) GetAllowAnySubjectOk() (*bool, bool)`

GetAllowAnySubjectOk returns a tuple with the AllowAnySubject field if it's non-nil, zero value otherwise
and a boolean to check if the value has been set.

### SetAllowAnySubject

`func (o *TrustJwtGrantIssuerBody) SetAllowAnySubject(v bool)`

SetAllowAnySubject sets AllowAnySubject field to given value.

### HasAllowAnySubject

`func (o *TrustJwtGrantIssuerBody) HasAllowAnySubject() bool`

HasAllowAnySubject returns a boolean if a field has been set.

### GetExpiresAt

`func (o *TrustJwtGrantIssuerBody) GetExpiresAt() time.Time`
Expand Down Expand Up @@ -128,6 +154,11 @@ and a boolean to check if the value has been set.

SetSubject sets Subject field to given value.

### HasSubject

`func (o *TrustJwtGrantIssuerBody) HasSubject() bool`

HasSubject returns a boolean if a field has been set.


[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
Expand Down
26 changes: 26 additions & 0 deletions internal/httpclient-next/docs/TrustedJwtGrantIssuer.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**AllowAnySubject** | Pointer to **bool** | The \&quot;allow_any_subject\&quot; indicates that the issuer is allowed to have any principal as the subject of the JWT. | [optional]
**CreatedAt** | Pointer to **time.Time** | The \&quot;created_at\&quot; indicates, when grant was created. | [optional]
**ExpiresAt** | Pointer to **time.Time** | The \&quot;expires_at\&quot; indicates, when grant will expire, so we will reject assertion from \&quot;issuer\&quot; targeting \&quot;subject\&quot;. | [optional]
**Id** | Pointer to **string** | | [optional]
Expand Down Expand Up @@ -31,6 +32,31 @@ NewTrustedJwtGrantIssuerWithDefaults instantiates a new TrustedJwtGrantIssuer ob
This constructor will only assign default values to properties that have it defined,
but it doesn't guarantee that properties required by API are set

### GetAllowAnySubject

`func (o *TrustedJwtGrantIssuer) GetAllowAnySubject() bool`

GetAllowAnySubject returns the AllowAnySubject field if non-nil, zero value otherwise.

### GetAllowAnySubjectOk

`func (o *TrustedJwtGrantIssuer) GetAllowAnySubjectOk() (*bool, bool)`

GetAllowAnySubjectOk returns a tuple with the AllowAnySubject field if it's non-nil, zero value otherwise
and a boolean to check if the value has been set.

### SetAllowAnySubject

`func (o *TrustedJwtGrantIssuer) SetAllowAnySubject(v bool)`

SetAllowAnySubject sets AllowAnySubject field to given value.

### HasAllowAnySubject

`func (o *TrustedJwtGrantIssuer) HasAllowAnySubject() bool`

HasAllowAnySubject returns a boolean if a field has been set.

### GetCreatedAt

`func (o *TrustedJwtGrantIssuer) GetCreatedAt() time.Time`
Expand Down
Loading

0 comments on commit a3c4304

Please sign in to comment.