diff --git a/admin-ui/app/locales/en/translation.json b/admin-ui/app/locales/en/translation.json index cf01c701b..da53e5587 100644 --- a/admin-ui/app/locales/en/translation.json +++ b/admin-ui/app/locales/en/translation.json @@ -169,6 +169,9 @@ "saml1_uri": "Saml1 URI", "saml2_uri": "Saml2 URI", "scope_type": "Scope type", + "umaAuthorizationPolicies": "Authorization Policies", + "umaResourcesScript": "Associated Clients", + "iconUrl": "Icon URL", "scopes:": "Scopes", "scripts": "Scripts", "script_type": "Script Type", @@ -405,7 +408,8 @@ "sql_passwordEncryptionMethod": "Add password encryption method.", "sql_serverTimezone": "Enter server timezone.", "activate_sql_configuration": "Activate SQL configuration.", - "script_path": "Enter script file path" + "script_path": "Enter script file path", + "iconUrl": "Icon URL" }, "titles": { "acrs": "ACRs", diff --git a/admin-ui/app/routes/Apps/Gluu/GluuTypeAheadForDn.js b/admin-ui/app/routes/Apps/Gluu/GluuTypeAheadForDn.js index 972597b71..d77505bce 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuTypeAheadForDn.js +++ b/admin-ui/app/routes/Apps/Gluu/GluuTypeAheadForDn.js @@ -24,6 +24,7 @@ function GluuTypeAheadForDn({ required, doc_category, doc_entry, + disabled = false, allowNew = false, lsize = 4, rsize = 8, @@ -48,6 +49,7 @@ function GluuTypeAheadForDn({ ), ) }} + disabled={disabled} id={name} data-testid={name} name={name} diff --git a/admin-ui/plugins/auth-server/components/Scopes/ScopeForm.js b/admin-ui/plugins/auth-server/components/Scopes/ScopeForm.js index 94a5a8a6d..ce6302255 100644 --- a/admin-ui/plugins/auth-server/components/Scopes/ScopeForm.js +++ b/admin-ui/plugins/auth-server/components/Scopes/ScopeForm.js @@ -31,20 +31,23 @@ import GluuTooltip from 'Routes/Apps/Gluu/GluuTooltip' import { SCOPE } from 'Utils/ApiResources' import { useTranslation } from 'react-i18next' import { ThemeContext } from 'Context/theme/themeContext' -import { - LIMIT, - PATTERN, -} from 'Plugins/auth-server/common/Constants' +import { LIMIT, PATTERN } from 'Plugins/auth-server/common/Constants' +import { getUMAResources } from '../../redux/actions/ScopeActions' -function ScopeForm({ scope, scripts, attributes, handleSubmit}) { +function ScopeForm({ scope, scripts, attributes, handleSubmit }) { const { t } = useTranslation() let dynamicScopeScripts = [] + let umaResourcesScript = [] + let umaAuthorizationPolicies = [] + const theme = useContext(ThemeContext) const selectedTheme = theme.state.theme const history = useHistory() const spontaneousClientScopes = scope.attributes.spontaneousClientScopes || [] - const dispatch = useDispatch() - const client = useSelector((state) => state.oidcReducer.items) + const dispatch = useDispatch() + const client = useSelector((state) => state.oidcReducer.items) + const umaResources = useSelector((state) => state.scopeReducer.umaResources) + // const scripts = useSelector((state) => state.customScriptReducer.items) let claims = [] scripts = scripts || [] attributes = attributes || [] @@ -52,17 +55,43 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { .filter((item) => item.scriptType == 'DYNAMIC_SCOPE') .filter((item) => item.enabled) .map((item) => ({ dn: item.dn, name: item.name })) + + umaAuthorizationPolicies = scripts + .filter((item) => item.scriptType == 'UMA_RPT_POLICY') + .filter((item) => item.enabled) + .map((item) => ({ dn: item.dn, name: item.name })) + + umaResourcesScript = umaResources.map((item) => ({ + dn: item.dn, + name: item.id, + })) + + useEffect(() => { + dispatch(getUMAResources({ pattern: 'scope' })) + }, []) + claims = attributes.map((item) => ({ dn: item.dn, name: item.displayName })) const [init, setInit] = useState(false) const [modal, setModal] = useState(false) - const [showClaimsPanel, handleClaimsPanel] = useState(scope.scopeType === 'openid') - const [showDynamicPanel, handleDynamicPanel] = useState(scope.scopeType === 'dynamic') - const [showSpontaneousPanel, handleShowSpontaneousPanel] = useState(scope.scopeType === 'spontaneous') + const [showClaimsPanel, handleClaimsPanel] = useState( + scope.scopeType === 'openid', + ) + const [showDynamicPanel, handleDynamicPanel] = useState( + scope.scopeType === 'dynamic', + ) + const [showSpontaneousPanel, handleShowSpontaneousPanel] = useState( + scope.scopeType === 'spontaneous', + ) + const [showUmaPanel, handleShowUmaPanel] = useState(scope.scopeType === 'UMA') useEffect(() => { if (showSpontaneousPanel) { - dispatch(searchClients({ "action_data": makeOptions(1, scope.attributes.spontaneousClientId) })) + dispatch( + searchClients({ + action_data: makeOptions(1, scope.attributes.spontaneousClientId), + }), + ) } }, [showClaimsPanel]) @@ -89,6 +118,11 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { } else { handleShowSpontaneousPanel(false) } + if (type && type === 'UMA') { + handleShowUmaPanel(true) + } else { + handleShowUmaPanel(false) + } scope.dynamicScopeScripts = '' scope.claims = '' scope.attributes.spontaneousClientId = '' @@ -143,9 +177,7 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { displayName: Yup.string() .min(2, 'displayName 2 characters') .required('Required!'), - id: Yup.string() - .min(2, 'id 2 characters') - .required('Required!'), + id: Yup.string().min(2, 'id 2 characters').required('Required!'), })} onSubmit={(values) => { const result = Object.assign(scope, values) @@ -162,7 +194,6 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { > {(formik) => (
- @@ -170,11 +201,7 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { )} - {showSpontaneousPanel ? - ( + {showSpontaneousPanel ? ( + @@ -262,8 +289,9 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { - ) : - ( + + ) : ( + @@ -282,6 +310,7 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { + @@ -289,7 +318,8 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { {(msg) =>
{msg}
}
-
)} +
+ )} {showDynamicPanel && ( )} + {showUmaPanel && ( + <> + + + + + + + + + {t('fields.umaAuthorizationPolicies').toUpperCase()} + + + + + + + + + {t('fields.umaResourcesScript').toUpperCase()} + + + + + + + + )} {showSpontaneousPanel && ( @@ -333,14 +420,23 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { doc_entry="spontaneousClientId" > - +
{scope.attributes.spontaneousClientId} - goToClientViewPage(scope.attributes.spontaneousClientId)}> + + goToClientViewPage( + scope.attributes.spontaneousClientId, + ) + } + > -
+
@@ -350,15 +446,23 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { doc_entry="spontaneousClientScopes" > - + - {scope?.attributes?.spontaneousClientScopes?.map((item, key) => ( -
- - {item} - -
- ))} + {scope?.attributes?.spontaneousClientScopes?.map( + (item, key) => ( +
+ + {item} + +
+ ), + )}
@@ -366,8 +470,7 @@ function ScopeForm({ scope, scripts, attributes, handleSubmit}) { )} - {!showSpontaneousPanel && - ()} + {!showSpontaneousPanel && } )} - + ) } -export default ScopeForm \ No newline at end of file +export default ScopeForm diff --git a/admin-ui/plugins/auth-server/plugin-metadata.js b/admin-ui/plugins/auth-server/plugin-metadata.js index a00fbc8f0..6799ad78c 100644 --- a/admin-ui/plugins/auth-server/plugin-metadata.js +++ b/admin-ui/plugins/auth-server/plugin-metadata.js @@ -25,6 +25,7 @@ import jsonSaga from './redux/sagas/JsonConfigSaga' import jwksSaga from './redux/sagas/JwksSaga' import acrSaga from './redux/sagas/AcrsSaga' import loggingSaga from './redux/sagas/LoggingSaga' +import umaResourceSaga from './redux/sagas/UMAResourcesSaga' import { ACR_READ, @@ -143,6 +144,7 @@ const pluginMetadata = { jwksSaga(), acrSaga(), loggingSaga(), + umaResourceSaga(), ], } diff --git a/admin-ui/plugins/auth-server/redux/actions/ScopeActions.js b/admin-ui/plugins/auth-server/redux/actions/ScopeActions.js index 651e99d4e..3c654075e 100644 --- a/admin-ui/plugins/auth-server/redux/actions/ScopeActions.js +++ b/admin-ui/plugins/auth-server/redux/actions/ScopeActions.js @@ -13,6 +13,8 @@ import { GET_SCOPE_BY_PATTERN, GET_SCOPE_BY_PATTERN_RESPONSE, SEARCH_SCOPES, + GET_UMA_RESOURCES, + GET_UMA_RESOURCES_RESPONSE, } from './types' export const getScopes = (action) => ({ @@ -84,3 +86,11 @@ export const setCurrentItem = (item) => ({ type: SET_ITEM, payload: { item }, }) +export const getUMAResources = (item) => ({ + type: GET_UMA_RESOURCES, + payload: { item }, +}) +export const getUMAResourcesResponse = (data) => ({ + type: GET_UMA_RESOURCES_RESPONSE, + payload: { data }, +}) diff --git a/admin-ui/plugins/auth-server/redux/actions/types.js b/admin-ui/plugins/auth-server/redux/actions/types.js index 4d7a29539..4a75a3d7d 100644 --- a/admin-ui/plugins/auth-server/redux/actions/types.js +++ b/admin-ui/plugins/auth-server/redux/actions/types.js @@ -8,6 +8,8 @@ export const RESET = 'RESET' export const SET_ITEM = 'SET_ITEM' export const USERINFO_REQUEST = 'USERINFO_REQUEST' export const USERINFO_RESPONSE = 'USERINFO_RESPONSE' +export const GET_UMA_RESOURCES = 'GET_UMA_RESOURCES' +export const GET_UMA_RESOURCES_RESPONSE = 'GET_UMA_RESOURCES_RESPONSE' // scopes export const GET_SCOPES = 'GET_SCOPES' diff --git a/admin-ui/plugins/auth-server/redux/api/ScopeApi.js b/admin-ui/plugins/auth-server/redux/api/ScopeApi.js index 731fcb98e..82f6b02d7 100644 --- a/admin-ui/plugins/auth-server/redux/api/ScopeApi.js +++ b/admin-ui/plugins/auth-server/redux/api/ScopeApi.js @@ -4,6 +4,7 @@ export default class ScopeApi { } getAllScopes = (options) => { + options['withAssociatedClients'] = true return new Promise((resolve, reject) => { this.api.getOauthScopes(options, (error, data) => { this.handleResponse(error, reject, resolve, data) diff --git a/admin-ui/plugins/auth-server/redux/api/UMAResourcesApi.js b/admin-ui/plugins/auth-server/redux/api/UMAResourcesApi.js new file mode 100644 index 000000000..85c72e5c2 --- /dev/null +++ b/admin-ui/plugins/auth-server/redux/api/UMAResourcesApi.js @@ -0,0 +1,21 @@ +export default class UMAResourcesApi { + constructor(api) { + this.api = api + } + + getUmaResources = async (opts) => { + return new Promise((resolve, reject) => { + this.api.getOauthUmaResources(opts, (error, data) => { + this.handleResponse(error, reject, resolve, data) + }) + }) + } + + handleResponse(error, reject, resolve, data) { + if (error) { + reject(error) + } else { + resolve(data) + } + } +} diff --git a/admin-ui/plugins/auth-server/redux/reducers/ScopeReducer.js b/admin-ui/plugins/auth-server/redux/reducers/ScopeReducer.js index f77b58c56..da3efcc00 100644 --- a/admin-ui/plugins/auth-server/redux/reducers/ScopeReducer.js +++ b/admin-ui/plugins/auth-server/redux/reducers/ScopeReducer.js @@ -14,6 +14,7 @@ import { GET_SCOPE_BY_PATTERN, GET_SCOPE_BY_PATTERN_RESPONSE, SEARCH_SCOPES, + GET_UMA_RESOURCES_RESPONSE, } from '../actions/types' import reducerRegistry from 'Redux/reducers/ReducerRegistry' const INIT_STATE = { @@ -22,6 +23,7 @@ const INIT_STATE = { loading: false, saveOperationFlag: false, errorInSaveOperationFlag: false, + umaResources: [], } const reducerName = 'scopeReducer' @@ -42,6 +44,12 @@ export default function scopeReducer(state = INIT_STATE, action) { } else { return handleDefault() } + case GET_UMA_RESOURCES_RESPONSE: + return { + ...state, + umaResources: action.payload.data, + loading: false, + } case GET_SCOPE_BY_INUM: return handleLoading() case GET_SCOPE_BY_INUM_RESPONSE: diff --git a/admin-ui/plugins/auth-server/redux/sagas/UMAResourcesSaga.js b/admin-ui/plugins/auth-server/redux/sagas/UMAResourcesSaga.js new file mode 100644 index 000000000..db79fc878 --- /dev/null +++ b/admin-ui/plugins/auth-server/redux/sagas/UMAResourcesSaga.js @@ -0,0 +1,75 @@ +import { + call, + all, + put, + fork, + takeEvery, + takeLatest, + select, +} from 'redux-saga/effects' +import { getAPIAccessToken } from '../actions/AuthActions' +import { + getScopesResponse, + getScopeByPatternResponse, + addScopeResponse, + editScopeResponse, + deleteScopeResponse, + getUMAResourcesResponse, +} from '../actions/ScopeActions' +import { + GET_SCOPES, + SEARCH_SCOPES, + GET_SCOPE_BY_INUM, + ADD_SCOPE, + EDIT_SCOPE, + DELETE_SCOPE, + GET_SCOPE_BY_PATTERN, + GET_UMA_RESOURCES, +} from '../actions/types' +import { SCOPE } from '../audit/Resources' +import { + CREATE, + UPDATE, + DELETION, + FETCH, +} from '../../../../app/audit/UserActionType' +import UMAResourcesApi from '../api/UMAResourcesApi' +import { getClient } from 'Redux/api/base' +import { isFourZeroOneError, addAdditionalData } from 'Utils/TokenController' +import { postUserAction } from 'Redux/api/backend-api' + +const JansConfigApi = require('jans_config_api') +import { initAudit } from 'Redux/sagas/SagaUtils' + +function* newFunction() { + const token = yield select((state) => state.authReducer.token.access_token) + const issuer = yield select((state) => state.authReducer.issuer) + const api = new JansConfigApi.OAuthUMAResourcesApi( + getClient(JansConfigApi, token, issuer), + ) + return new UMAResourcesApi(api) +} + +export function* getUmaResources({ payload }) { + const audit = yield* initAudit() + try { + addAdditionalData(audit, FETCH, SCOPE, payload) + const umaResourcesApi = yield* newFunction() + const data = yield call(umaResourcesApi.getUmaResources, payload.item) + yield put(getUMAResourcesResponse(data)) + yield call(postUserAction, audit) + } catch (e) { + if (isFourZeroOneError(e)) { + const jwt = yield select((state) => state.authReducer.userinfo_jwt) + yield put(getAPIAccessToken(jwt)) + } + } +} + +export function* watchGetUmaResources() { + yield takeLatest(GET_UMA_RESOURCES, getUmaResources) +} + +export default function* rootSaga() { + yield all([fork(watchGetUmaResources)]) +}