Retrieve role information the Redfish standard way
Currently webui-vue has a hardcoded list of pages and sidebar menu items
restricted to a specific Redfish role (from a predefined default set).
To disallow navigating to restricted pages and to hide disallowed menu
items the application needs to know the roles assigned to the session.
bmcweb only implements a single role identity per session so the Roles
array returned within a Session object always has just one element.
This patch changes the mechanism used to retrieve the current role from
buggy direct query to AccountService (which can only return information
about BMC local users) to extracting it from standard Redfish Session
object.
In case the role is not available (e.g. when backend implementation
predates #Session.v1_7_0.Session) the application assumes Administrator
role which is meant as a best effort to continue working given the
circumstances. This doesn't pose a security risk because all validation
is always performed by the backend itself, so the worst that can happen
is end user getting error messages trying to access something without
enough privileges.
Tested: logging in and out of accounts with different roles without
reloading the page, observing the list of queries made, the role
variable assignments and presence of the menu items depending on
account, navigating to different pages. Also tested reloading the page
and confirmed the correct role was retrieved without going through login
again. Also tested deleting and mangling localStorage variable
sessionURI prior to doing page reload, in those cases redirect to login
page was observed.
Change-Id: I8b6c84060a987489cc1d35c46c1b00618a88b607
Signed-off-by: Paul Fertser <fercerpav@gmail.com>
diff --git a/src/router/index.js b/src/router/index.js
index 22a3a8c..27fd96e 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -42,11 +42,17 @@
// condition will get satisfied if user refreshed after login
if (!currentUserRole && store.getters['authentication/isLoggedIn']) {
// invoke API call to get the role ID
- let username = localStorage.getItem('storedUsername');
- store.dispatch('authentication/getUserInfo', username).then(() => {
- let currentUserRole = store.getters['global/userPrivilege'];
- allowRouterToNavigate(to, next, currentUserRole);
- });
+ store
+ .dispatch('authentication/getSessionPrivilege')
+ .then(() => {
+ let currentUserRole = store.getters['global/userPrivilege'];
+ allowRouterToNavigate(to, next, currentUserRole);
+ })
+ // our store got out of sync, start afresh
+ .catch(() => {
+ console.log('Failed to obtain current Roles, logging out.');
+ store.dispatch('authentication/logout');
+ });
} else {
allowRouterToNavigate(to, next, currentUserRole);
}
diff --git a/src/store/api.js b/src/store/api.js
index babed4c..0e119c2 100644
--- a/src/store/api.js
+++ b/src/store/api.js
@@ -39,7 +39,7 @@
// Check if action is unauthorized.
if (response.status == 403) {
- if (isPasswordExpired(response)) {
+ if (isPasswordExpired(response.data)) {
router.push('/change-password');
} else {
// Toast error message will appear on screen.
@@ -92,8 +92,8 @@
};
};
-export const isPasswordExpired = (response) => {
- let extInfoMsgs = response?.data?.['@Message.ExtendedInfo'];
+export const isPasswordExpired = (data) => {
+ let extInfoMsgs = data?.['@Message.ExtendedInfo'];
return (
extInfoMsgs &&
extInfoMsgs.find(
diff --git a/src/store/modules/Authentication/AuthenticanStore.js b/src/store/modules/Authentication/AuthenticanStore.js
index 3122ab2..e876f78 100644
--- a/src/store/modules/Authentication/AuthenticanStore.js
+++ b/src/store/modules/Authentication/AuthenticanStore.js
@@ -68,12 +68,13 @@
UserName: username,
Password: password,
})
- .then((response) => {
+ .then(({ headers, data }) => {
commit('authSuccess', {
- session: response.headers['location'],
- token: response.headers['x-auth-token'],
+ session: headers['location'],
+ token: headers['x-auth-token'],
});
- return isPasswordExpired(response);
+ setSessionPrivilege(commit, data);
+ return isPasswordExpired(data);
})
.catch((error) => {
commit('authError');
@@ -83,27 +84,19 @@
logout({ commit, state }) {
api
.delete(state.sessionURI)
+ .catch(() =>
+ console.log(
+ "Couldn't DELETE Session, proceeding with the logout anyway to get in sync with the backend.",
+ ),
+ )
.then(() => commit('logout'))
.then(() => router.push('/login'))
.catch((error) => console.log(error));
},
- getUserInfo({ commit }, username) {
+ getSessionPrivilege({ commit, state }) {
return api
- .get(`/redfish/v1/AccountService/Accounts/${username}`)
- .then(({ data }) => {
- commit('global/setPrivilege', data.RoleId, { root: true });
- return data;
- })
- .catch((error) => {
- if (error.response?.status === 404) {
- // We have valid credentials but user isn't known, assume remote
- // authentication (e.g. LDAP) and do not restrict the routing
- commit('global/setPrivilege', roles.administrator, { root: true });
- return {};
- } else {
- console.log(error);
- }
- });
+ .get(state.sessionURI)
+ .then(({ data }) => setSessionPrivilege(commit, data));
},
resetStoreState({ state }) {
state.authError = false;
@@ -113,4 +106,14 @@
},
};
+const setSessionPrivilege = (commit, data) => {
+ // If the backend didn't provide the role information in the Session object
+ // our best bet is to assume the Administrator role to avoid hiding
+ // potentially useful UI elements. Everything security-sensitive is validated
+ // on the backend side anyway, so this is safe.
+ commit('global/setPrivilege', data.Roles?.[0] ?? roles.administrator, {
+ root: true,
+ });
+};
+
export default AuthenticationStore;