Skip to content

Commit

Permalink
limit access to users with ROLE_OAUTH_ADMIN (#58)
Browse files Browse the repository at this point in the history
* limit access to users with ROLE_OAUTH_ADMIN

* update to README
  • Loading branch information
thomasridd committed Feb 1, 2024
1 parent 21f668b commit d4a5fb6
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 12 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
[![repo standards badge](https://img.shields.io/badge/dynamic/json?color=blue&style=flat&logo=github&label=MoJ%20Compliant&query=%24.result&url=https%3A%2F%2Foperations-engineering-reports.cloud-platform.service.justice.gov.uk%2Fapi%2Fv1%2Fcompliant_public_repositories%2Fhmpps-authorization)](https://operations-engineering-reports.cloud-platform.service.justice.gov.uk/public-github-repositories.html#hmpps-authorization "Link to report")
[![CircleCI](https://circleci.com/gh/ministryofjustice/hmpps-authorization/tree/main.svg?style=svg)](https://circleci.com/gh/ministryofjustice/hmpps-authorization)

Admin interface for managing clients and users in the new OAuth2 authorization service `hmpps-authorization-server` [here](https://github.com/ministryofjustice/hmpps-authorization-server).


### Note on update to authorization and authentication

At time of this README update (Jan 2024) authorization and authentication services are handled by the full stack Kotlin app `hmpps-auth`. This also includes a Kotlin frontend for managing clients.

Due to authorization libraries used by `hmpps-auth` being deprecated authorization functionality is being extracted to its own service `hmpps-authorization-server`.

The frontend for managing clients is being extracted to this repo `hmpps-authorization`.

# Instructions

## Running the app
Expand Down
42 changes: 39 additions & 3 deletions integration_tests/e2e/add-base-client.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import Page from '../pages/page'
import AddBaseClientGrantPage from '../pages/addBaseClientGrant'
import AddBaseClientDetailsPage from '../pages/addBaseClientDetails'
import ViewBaseClientListPage from '../pages/viewBaseClientList'
import AuthErrorPage from '../pages/authError'
import AuthSignInPage from '../pages/authSignIn'

const visitAddBaseClientPage = (): AddBaseClientGrantPage => {
cy.signIn({ failOnStatusCode: true, redirectPath: '/base-clients/new' })
const visitAddBaseClientPage = (options = { failOnStatusCode: true }): AddBaseClientGrantPage => {
const { failOnStatusCode } = options
cy.signIn({ failOnStatusCode, redirectPath: '/base-clients/new' })
return Page.verifyOnPage(AddBaseClientGrantPage)
}

Expand All @@ -16,18 +19,51 @@ const visitAddWithClientCredentialsPage = (): AddBaseClientDetailsPage => {
context('Add client page', () => {
beforeEach(() => {
cy.task('reset')
cy.task('stubSignIn')
cy.task('stubSignIn', ['ROLE_OAUTH_ADMIN'])
cy.task('stubManageUser')
cy.task('stubListBaseClients')
})

context('Authorisation and authentication', () => {
it('Unauthenticated user directed to auth', () => {
cy.visit('/base-clients/new')
Page.verifyOnPage(AuthSignInPage)
})

it('Unauthenticated user accessing details directed to auth', () => {
cy.visit('/base-clients/new?grant=client-credentials')
Page.verifyOnPage(AuthSignInPage)
})

it('User without ROLE_OAUTH_ADMIN role denied access to grant screen', () => {
cy.task('stubSignIn', ['ROLE_OTHER'])
cy.signIn({ failOnStatusCode: false, redirectPath: '/base-clients/new' })

Page.verifyOnPage(AuthErrorPage)
})

it('User without ROLE_OAUTH_ADMIN role denied access to details screen', () => {
cy.task('stubSignIn', ['ROLE_OTHER'])
cy.signIn({ failOnStatusCode: false, redirectPath: '/base-clients/new?grant=client-credentials' })

Page.verifyOnPage(AuthErrorPage)
})
})

context('Add base client choose grant screen', () => {
let addBaseClientGrantPage: AddBaseClientGrantPage

beforeEach(() => {
addBaseClientGrantPage = visitAddBaseClientPage()
})

it('User without ROLE_OAUTH_ADMIN role denied access', () => {
cy.task('stubSignIn', ['ROLE_OTHER'])
cy.signIn({ failOnStatusCode: false, redirectPath: '/base-clients/new' })

Page.verifyOnPage(AuthErrorPage)
})

it('User can see select a grant type radio buttons', () => {
addBaseClientGrantPage.grantTypeRadioGroup().should('be.visible')
})
Expand Down
25 changes: 25 additions & 0 deletions integration_tests/e2e/edit-base-client-deployment.cy.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
import Page from '../pages/page'
import ViewBaseClientPage from '../pages/viewBaseClient'
import EditBaseClientDeploymentDetailsPage from '../pages/editBaseClientDeploymentDetails'
import AuthSignInPage from '../pages/authSignIn'
import AuthErrorPage from '../pages/authError'

const visitEditBaseClientDeploymentDetailsPage = (): EditBaseClientDeploymentDetailsPage => {
cy.signIn({ failOnStatusCode: true, redirectPath: '/base-clients/base_client_id_1/deployment' })
return Page.verifyOnPage(EditBaseClientDeploymentDetailsPage)
}

context('Edit base client deployment: Auth', () => {
beforeEach(() => {
cy.task('reset')
cy.task('stubSignIn')
cy.task('stubManageUser')
cy.task('stubListBaseClients')
cy.task('stubGetBaseClient')
cy.task('stubGetListClientInstancesList')
})

it('Unauthenticated user directed to auth', () => {
cy.visit('/base-clients/base_client_id_1/deployment')
Page.verifyOnPage(AuthSignInPage)
})

it('User without ROLE_OAUTH_ADMIN role denied access', () => {
cy.task('stubSignIn', ['ROLE_OTHER'])
cy.signIn({ failOnStatusCode: false, redirectPath: '/base-clients/base_client_id_1/deployment' })

Page.verifyOnPage(AuthErrorPage)
})
})

context('Edit base client deployment details page', () => {
let editBaseClientDeploymentDetailsPage: EditBaseClientDeploymentDetailsPage

Expand Down
25 changes: 25 additions & 0 deletions integration_tests/e2e/edit-base-client-details.cy.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
import Page from '../pages/page'
import EditBaseClientDetailsPage from '../pages/editBaseClientDetails'
import ViewBaseClientPage from '../pages/viewBaseClient'
import AuthSignInPage from '../pages/authSignIn'
import AuthErrorPage from '../pages/authError'

const visitEditBaseClientDetailsPage = (): EditBaseClientDetailsPage => {
cy.signIn({ failOnStatusCode: true, redirectPath: '/base-clients/base_client_id_1/edit' })
return Page.verifyOnPage(EditBaseClientDetailsPage)
}

context('Edit base client details: Auth', () => {
beforeEach(() => {
cy.task('reset')
cy.task('stubSignIn')
cy.task('stubManageUser')
cy.task('stubListBaseClients')
cy.task('stubGetBaseClient')
cy.task('stubGetListClientInstancesList')
})

it('Unauthenticated user directed to auth', () => {
cy.visit('/base-clients/base_client_id_1/deployment')
Page.verifyOnPage(AuthSignInPage)
})

it('User without ROLE_OAUTH_ADMIN role denied access', () => {
cy.task('stubSignIn', ['ROLE_OTHER'])
cy.signIn({ failOnStatusCode: false, redirectPath: '/base-clients/base_client_id_1/edit' })

Page.verifyOnPage(AuthErrorPage)
})
})

context('Edit base client details page', () => {
let editBaseClientDetailsPage: EditBaseClientDetailsPage

Expand Down
10 changes: 9 additions & 1 deletion integration_tests/e2e/login.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import IndexPage from '../pages/index'
import AuthSignInPage from '../pages/authSignIn'
import Page from '../pages/page'
import AuthManageDetailsPage from '../pages/authManageDetails'
import AuthErrorPage from '../pages/authError'

context('SignIn', () => {
beforeEach(() => {
cy.task('reset')
cy.task('stubSignIn')
cy.task('stubSignIn', ['ROLE_OAUTH_ADMIN'])
cy.task('stubListBaseClients')
cy.task('stubGetBaseClient')
cy.task('stubManageUser')
Expand All @@ -22,6 +23,13 @@ context('SignIn', () => {
Page.verifyOnPage(AuthSignInPage)
})

it('User without ROLE_OAUTH_ADMIN role denied access', () => {
cy.task('stubSignIn', ['ROLE_OTHER'])

cy.signIn({ failOnStatusCode: false })
Page.verifyOnPage(AuthErrorPage)
})

it('User name visible in header', () => {
cy.signIn()
const indexPage = Page.verifyOnPage(IndexPage)
Expand Down
25 changes: 25 additions & 0 deletions integration_tests/e2e/view-base-client-list.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,38 @@ import Page from '../pages/page'
import ViewBaseClientListPage from '../pages/viewBaseClientList'
import ViewBaseClientPage from '../pages/viewBaseClient'
import NewBaseClientGrantPage from '../pages/newBaseClientGrant'
import AuthSignInPage from '../pages/authSignIn'
import AuthErrorPage from '../pages/authError'

const visitBaseClientListPage = (): ViewBaseClientListPage => {
cy.signIn()
cy.visit('/')
return Page.verifyOnPage(ViewBaseClientListPage)
}

context('Homepage - Auth', () => {
beforeEach(() => {
cy.task('reset')
cy.task('stubSignIn')
cy.task('stubListBaseClients')
cy.task('stubGetBaseClient')
cy.task('stubManageUser')
cy.task('stubGetListClientInstancesList')
})

it('Unauthenticated user directed to auth', () => {
cy.visit('/')
Page.verifyOnPage(AuthSignInPage)
})

it('User without ROLE_OAUTH_ADMIN role denied access', () => {
cy.task('stubSignIn', ['ROLE_OTHER'])
cy.signIn({ failOnStatusCode: false, redirectPath: '/' })

Page.verifyOnPage(AuthErrorPage)
})
})

context('Homepage - list base-clients', () => {
let listBaseClientsPage: ViewBaseClientListPage

Expand Down
25 changes: 25 additions & 0 deletions integration_tests/e2e/view-base-client.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,37 @@ import ViewClientSecretsPage from '../pages/viewClientSecrets'
import ConfirmDeleteClientPage from '../pages/confirmDeleteClient'
import EditBaseClientDetailsPage from '../pages/editBaseClientDetails'
import EditBaseClientDeploymentDetailsPage from '../pages/editBaseClientDeploymentDetails'
import AuthSignInPage from '../pages/authSignIn'
import AuthErrorPage from '../pages/authError'

const visitBaseClientPage = (): ViewBaseClientPage => {
cy.signIn({ failOnStatusCode: true, redirectPath: '/base-clients/base_client_id_1' })
return Page.verifyOnPage(ViewBaseClientPage)
}

context('Base client page - Auth', () => {
beforeEach(() => {
cy.task('reset')
cy.task('stubSignIn')
cy.task('stubGetBaseClient')
cy.task('stubManageUser')
cy.task('stubGetListClientInstancesList')
cy.task('stubAddClientInstance')
})

it('Unauthenticated user directed to auth', () => {
cy.visit('/base-clients/base_client_id_1')
Page.verifyOnPage(AuthSignInPage)
})

it('User without ROLE_OAUTH_ADMIN role denied access', () => {
cy.task('stubSignIn', ['ROLE_OTHER'])
cy.signIn({ failOnStatusCode: false, redirectPath: '/base-clients/base_client_id_1' })

Page.verifyOnPage(AuthErrorPage)
})
})

context('Base client page', () => {
let baseClientsPage: ViewBaseClientPage

Expand Down
16 changes: 10 additions & 6 deletions integration_tests/mockApis/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { Response } from 'superagent'
import { stubFor, getMatchingRequests } from './wiremock'
import tokenVerification from './tokenVerification'

const createToken = () => {
const createToken = (roles: string[] = []) => {
// authorities in the session are always prefixed by ROLE.
const authorities = roles.map(role => (role.startsWith('ROLE_') ? role : `ROLE_${role}`))
const payload = {
user_name: 'USER1',
scope: ['read'],
auth_source: 'nomis',
authorities: [],
authorities,
jti: '83b50a10-cca6-41db-985f-e87efb303ddb',
client_id: 'clientid',
}
Expand Down Expand Up @@ -95,7 +97,7 @@ const manageDetails = () =>
},
})

const token = () =>
const token = (roles: string[] = []) =>
stubFor({
request: {
method: 'POST',
Expand All @@ -108,7 +110,7 @@ const token = () =>
Location: 'http://localhost:3007/sign-in/callback?code=codexxxx&state=stateyyyy',
},
jsonBody: {
access_token: createToken(),
access_token: createToken(roles),
token_type: 'bearer',
user_name: 'USER1',
expires_in: 599,
Expand All @@ -120,6 +122,8 @@ const token = () =>
export default {
getSignInUrl,
stubAuthPing: ping,
stubSignIn: (): Promise<[Response, Response, Response, Response, Response, Response]> =>
Promise.all([favicon(), redirect(), signOut(), manageDetails(), token(), tokenVerification.stubVerifyToken()]),
stubSignIn: (
roles: string[] = ['ROLE_OAUTH_ADMIN'],
): Promise<[Response, Response, Response, Response, Response, Response]> =>
Promise.all([favicon(), redirect(), signOut(), manageDetails(), token(roles), tokenVerification.stubVerifyToken()]),
}
7 changes: 7 additions & 0 deletions integration_tests/pages/authError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Page from './page'

export default class AuthErrorPage extends Page {
constructor() {
super('Authorisation Error')
}
}
2 changes: 1 addition & 1 deletion integration_tests/support/commands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Cypress.Commands.add('signIn', (options = { failOnStatusCode: true, redirectPath: '/' }) => {
const { failOnStatusCode, redirectPath } = options
cy.request(redirectPath)
cy.request(redirectPath || '/')
return cy.task('getSignInUrl').then((url: string) => cy.visit(url, { failOnStatusCode }))
})
2 changes: 1 addition & 1 deletion server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function createApp(services: Services): express.Application {
app.use(setUpStaticResources())
nunjucksSetup(app, services.applicationInfo)
app.use(setUpAuthentication())
app.use(authorisationMiddleware())
app.use(authorisationMiddleware(['ROLE_OAUTH_ADMIN']))
app.use(setUpCsrf())
app.use(setUpCurrentUser(services))

Expand Down

0 comments on commit d4a5fb6

Please sign in to comment.