-
Notifications
You must be signed in to change notification settings - Fork 16
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
fix: check authorities in app adapter [LIBS-370] #757
base: master
Are you sure you want to change the base?
Changes from 1 commit
ce2938b
b5dcd5f
858546c
b03b858
b882ed0
ea3ae21
7f97ca5
1fe6278
768e48d
431fa72
7fb97ef
6ddc7e1
09b100c
0668608
d5ed304
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -7,9 +7,13 @@ import PropTypes from 'prop-types' | ||||||||||
import React, { useState } from 'react' | |||||||||||
import { LoadingMask } from './LoadingMask' | |||||||||||
|
|||||||||||
// TODO: Remove useVerifyLatestUser | |||||||||||
// TODO: add actual appName to config; rename existing to appTitle (& refactor) | |||||||||||
// - will also need to change app-runtime and headerbar-ui APIs | |||||||||||
// TODO: Remove useVerifyLatestUser.js (and in app wrapper) | |||||||||||
|
|||||||||||
const LATEST_USER_KEY = 'dhis2.latestUser' | |||||||||||
const IS_PRODUCTION_ENV = process.env.NODE_ENV === 'production' | |||||||||||
|
|||||||||||
const APP_MANAGER_AUTHORITY = 'M_dhis-web-maintenance-appmanager' | |||||||||||
const REQUIRED_APP_AUTHORITY = process.env.REACT_APP_DHIS2_APP_AUTH_NAME | |||||||||||
|
|||||||||||
const USER_QUERY = { | |||||||||||
user: { | |||||||||||
|
@@ -18,21 +22,6 @@ const USER_QUERY = { | ||||||||||
}, | |||||||||||
} | |||||||||||
|
|||||||||||
const LATEST_USER_KEY = 'dhis2.latestUser' | |||||||||||
|
|||||||||||
const getRequiredAppAuthority = (appName) => { | |||||||||||
// TODO: this only works for installed, non-core apps. Need other logic for those (dhis-web-app-name) | |||||||||||
// Maybe add this logic to CLI add this to config, instead of needing more env vars here | |||||||||||
// Need 'coreApp', 'name', 'title' (rename current appName to appTitle) | |||||||||||
return ( | |||||||||||
'M_' + | |||||||||||
appName | |||||||||||
.trim() | |||||||||||
.replaceAll(/[^a-zA-Z0-9\s]/g, '') | |||||||||||
.replaceAll(/\s/g, '_') | |||||||||||
) | |||||||||||
} | |||||||||||
|
|||||||||||
async function clearCachesIfUserChanged({ currentUserId, pwaEnabled }) { | |||||||||||
const latestUserId = localStorage.getItem(LATEST_USER_KEY) | |||||||||||
if (currentUserId !== latestUserId) { | |||||||||||
|
@@ -46,6 +35,20 @@ async function clearCachesIfUserChanged({ currentUserId, pwaEnabled }) { | ||||||||||
} | |||||||||||
} | |||||||||||
|
|||||||||||
const isAppAvailable = (authorities) => { | |||||||||||
// Skip check on dev | |||||||||||
// TODO: should we check on dev environments too? | |||||||||||
if (!IS_PRODUCTION_ENV) { | |||||||||||
return true | |||||||||||
} | |||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious what others think -- most app devs probably have admin privileges, but if they don't have app management authority, it's possible that someone might end up working on an a new app which they don't have authorities for. Someone might be able to bypass their authorities limitations by running an app locally though?... Currently I guess we don't do any checks like this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (You can make testing easier by commenting out the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I initially wasn't sold on the idea of always returning true in a development environment, but think I'm coming around to it.
This is actually very consistent and I like it. |
|||||||||||
// Check for three possible authorities | |||||||||||
return authorities.some((authority) => | |||||||||||
['ALL', APP_MANAGER_AUTHORITY, REQUIRED_APP_AUTHORITY].includes( | |||||||||||
authority | |||||||||||
) | |||||||||||
) | |||||||||||
} | |||||||||||
|
|||||||||||
/** | |||||||||||
* This hook is used to clear sensitive caches if a user other than the one | |||||||||||
* that cached that data logs in | |||||||||||
|
@@ -56,8 +59,6 @@ export function AuthBoundary({ children }) { | ||||||||||
const [finished, setFinished] = useState(false) | |||||||||||
const { loading, error, data } = useDataQuery(USER_QUERY, { | |||||||||||
onComplete: async ({ user }) => { | |||||||||||
console.log({ authorities: user.authorities }) | |||||||||||
|
|||||||||||
await clearCachesIfUserChanged({ | |||||||||||
currentUserId: user.id, | |||||||||||
pwaEnabled, | |||||||||||
|
@@ -74,21 +75,13 @@ export function AuthBoundary({ children }) { | ||||||||||
throw new Error('Failed to fetch user ID: ' + error) | |||||||||||
} | |||||||||||
|
|||||||||||
if (data) { | |||||||||||
const userHasAllAuthority = data.user.authorities.includes('ALL') | |||||||||||
|
|||||||||||
const requiredAppAuthority = getRequiredAppAuthority(appName) | |||||||||||
console.log({ requiredAppAuthority }) | |||||||||||
|
|||||||||||
const userHasRequiredAppAuthority = | |||||||||||
data.user.authorities.includes(requiredAppAuthority) | |||||||||||
if (!userHasAllAuthority && !userHasRequiredAppAuthority) { | |||||||||||
// TODO: better UI element than error boundary? | |||||||||||
throw new Error('Forbidden: not authorized to view this app') | |||||||||||
} | |||||||||||
if (isAppAvailable(data.user.authorities)) { | |||||||||||
return children | |||||||||||
} else { | |||||||||||
throw new Error( | |||||||||||
`Forbidden: you don't have access to the ${appName} app` | |||||||||||
) | |||||||||||
} | |||||||||||
|
|||||||||||
return children | |||||||||||
} | |||||||||||
AuthBoundary.propTypes = { | |||||||||||
children: PropTypes.node, | |||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const APP_AUTH_PREFIX = 'M_' | ||
const DHIS_WEB = 'dhis-web-' | ||
|
||
/** | ||
* Returns the string that identifies the 'App view permission' | ||
* required to view the app | ||
* | ||
* Ex: coreApp && name = 'data-visualizer': authName = 'M_dhis-web-data-visualizer' | ||
* Ex: name = 'pwa-example': authName = 'M_pwaexample' | ||
* Ex: name = 'BNA Action Tracker': authName = 'M_BNA_Action_Tracker' | ||
*/ | ||
const formatAppAuthName = (config) => { | ||
if (config.coreApp) { | ||
// TODO: Verify this formatting - are there any transformations, | ||
// or do we trust that it's lower-case and hyphenated? | ||
return APP_AUTH_PREFIX + DHIS_WEB + config.name | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious about whether this formatting of core app names is correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the user app I am also inferring specific types of authorities based on their naming scheme. This has been in production for about 4 years now, so this suggests to me the naming scheme is consistent enough. You can also review the response of this requests: https://play.dhis2.org/dev/api/40/authorities?fields=id,name&paging=false There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see we're doing that below - but we should also do it for core apps. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, notice that the formatting is different between those two things you linked, namely around hyphens -- core apps seem to be getting a different treatment? But I didn't find what exactly it is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added some formatting insurance in this commit |
||
|
||
// This formatting is drawn from https://github.com/dhis2/dhis2-core/blob/master/dhis-2/dhis-api/src/main/java/org/hisp/dhis/appmanager/App.java#L494-L499 | ||
// (replaceAll is only introduced in Node 15) | ||
return ( | ||
APP_AUTH_PREFIX + | ||
config.name | ||
.trim() | ||
.replace(/[^a-zA-Z0-9\s]/g, '') | ||
.replace(/\s/g, '_') | ||
) | ||
} | ||
|
||
module.exports = formatAppAuthName |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
const formatAppAuthName = require('./formatAppAuthName.js') | ||
|
||
describe('core app handling', () => { | ||
it('should handle core apps', () => { | ||
const config = { coreApp: true, name: 'data-visualizer' } | ||
const formattedAuthName = formatAppAuthName(config) | ||
|
||
expect(formattedAuthName).toBe('M_dhis-web-data-visualizer') | ||
}) | ||
}) | ||
|
||
describe('non-core app handling', () => { | ||
it('should handle app names with hyphens', () => { | ||
const config = { name: 'hyphenated-string-example' } | ||
const formattedAuthName = formatAppAuthName(config) | ||
|
||
expect(formattedAuthName).toBe('M_hyphenatedstringexample') | ||
}) | ||
|
||
it('should handle app names with capitals and spaces', () => { | ||
const config = { name: 'Multi Word App Name' } | ||
const formattedAuthName = formatAppAuthName(config) | ||
|
||
expect(formattedAuthName).toBe('M_Multi_Word_App_Name') | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in CLI