🔀 Merge pull request #171 from Lissy93/FEATURE/granular-access-165

[FEATURE] Granular User Access
Fixes #165
This commit is contained in:
Alicia Sykes
2021-08-20 22:33:33 +01:00
committed by GitHub
12 changed files with 165 additions and 21 deletions

View File

@@ -102,7 +102,7 @@ export default {
methods: {
shouldAllowWriteToDisk() {
const { appConfig } = this.config;
return appConfig.allowConfigEdit !== false && isUserAdmin(appConfig.auth);
return appConfig.allowConfigEdit !== false && isUserAdmin();
},
save() {
if (this.saveMode === 'local' || !this.allowWriteToDisk) {

View File

@@ -8,6 +8,7 @@
:rows="displayData.rows"
:color="displayData.color"
:customStyles="displayData.customStyles"
v-if="isSectionVisibleToUser()"
>
<div v-if="!items || items.length < 1" class="no-items">
No Items to Show Yet
@@ -51,6 +52,7 @@
import Item from '@/components/LinkItems/Item.vue';
import Collapsable from '@/components/LinkItems/Collapsable.vue';
import IframeModal from '@/components/LinkItems/IframeModal.vue';
import { getCurrentUser, isLoggedInAsGuest } from '@/utils/Auth';
export default {
name: 'ItemGroup',
@@ -85,6 +87,9 @@ export default {
? `grid-template-rows: repeat(${this.displayData.itemCountY}, 1fr);` : '';
return styles;
},
currentUser() {
return getCurrentUser();
},
},
methods: {
/* Returns a unique lowercase string, based on name, for section ID */
@@ -95,9 +100,11 @@ export default {
triggerModal(url) {
this.$refs[`iframeModal-${this.groupId}`].show(url);
},
/* Emmit value upwards when iframe modal opened/ closed */
modalChanged(changedTo) {
this.$emit('change-modal-visibility', changedTo);
},
/* Determines if user has enabled online status checks */
shouldEnableStatusCheck(itemPreference) {
const globalPreference = this.config.appConfig.statusCheck || false;
return itemPreference !== undefined ? itemPreference : globalPreference;
@@ -109,6 +116,35 @@ export default {
if (interval < 1) interval = 0;
return interval;
},
/* Returns false if this section should not be rendered for the current user/ guest */
isSectionVisibleToUser() {
const determineVisibility = (visibilityList, currentUser) => {
let isFound = false;
visibilityList.forEach((userInList) => {
if (userInList.toLowerCase() === currentUser) isFound = true;
});
return isFound;
};
const checkVisiblity = () => {
if (!this.currentUser) return true;
const hideFor = this.displayData.hideForUsers || [];
const currentUser = this.currentUser.user.toLowerCase();
return !determineVisibility(hideFor, currentUser);
};
const checkHiddenability = () => {
if (!this.currentUser) return true;
const currentUser = this.currentUser.user.toLowerCase();
const showForUsers = this.displayData.showForUsers || [];
if (showForUsers.length < 1) return true;
return determineVisibility(showForUsers, currentUser);
};
const checkIfHideForGuest = () => {
const hideForGuest = this.displayData.hideForGuests;
const isGuest = isLoggedInAsGuest();
return !(hideForGuest && isGuest);
};
return checkVisiblity() && checkHiddenability() && checkIfHideForGuest();
},
},
};
</script>

View File

@@ -114,7 +114,7 @@ export default {
* then they will never be able to view the homepage, so no button needed
*/
userState() {
return getUserState(this.appConfig || {});
return getUserState();
},
},
data() {

View File

@@ -31,7 +31,7 @@ const isGuestEnabled = () => {
/* Returns true if user is already authenticated, or if auth is not enabled */
const isAuthenticated = () => {
const users = config.appConfig.auth;
return (!users || users.length === 0 || isLoggedIn(users) || isGuestEnabled());
return (!users || users.length === 0 || isLoggedIn() || isGuestEnabled());
};
/* Get the users chosen starting view from app config, or return default */

View File

@@ -1,5 +1,19 @@
import sha256 from 'crypto-js/sha256';
import { cookieKeys, localStorageKeys, userStateEnum } from './defaults';
import ConfigAccumulator from '@/utils/ConfigAccumalator';
import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults';
/* Uses config accumulator to get and return app config */
const getAppConfig = () => {
const Accumulator = new ConfigAccumulator();
const config = Accumulator.config();
return config.appConfig || {};
};
/* Returns the users array from appConfig, if available, else an empty array */
const getUsers = () => {
const appConfig = getAppConfig();
return appConfig.auth || [];
};
/**
* Generates a 1-way hash, in order to be stored in local storage for authentication
@@ -17,7 +31,8 @@ const generateUserToken = (user) => {
* @param {Array[Object]} users An array of user objects pulled from the config
* @returns {Boolean} Will return true if the user is logged in, else false
*/
export const isLoggedIn = (users) => {
export const isLoggedIn = () => {
const users = getUsers();
const validTokens = users.map((user) => generateUserToken(user));
let userAuthenticated = false;
document.cookie.split(';').forEach((cookie) => {
@@ -35,10 +50,16 @@ export const isLoggedIn = (users) => {
};
/* Returns true if authentication is enabled */
export const isAuthEnabled = (users) => (users && users.length > 0);
export const isAuthEnabled = () => {
const users = getUsers();
return (users.length > 0);
};
/* Returns true if guest access is enabled */
export const isGuestAccessEnabled = (appConfig) => appConfig.enableGuestAccess || false;
export const isGuestAccessEnabled = () => {
const appConfig = getAppConfig();
return appConfig.enableGuestAccess || false;
};
/**
* Checks credentials entered by the user against those in the config
@@ -92,6 +113,33 @@ export const logout = () => {
localStorage.removeItem(localStorageKeys.USERNAME);
};
/**
* If correctly logged in as a valid, authenticated user,
* then returns the user object for the current user
* If not logged in, will return false
* */
export const getCurrentUser = () => {
if (!isLoggedIn()) return false; // User not logged in
const username = localStorage[localStorageKeys.USERNAME]; // Get username
if (!username) return false; // No username
let foundUserObject = false; // Value to return
getUsers().forEach((user) => {
// If current logged in user found, then return that user
if (user.user === username) foundUserObject = user;
});
return foundUserObject;
};
/**
* Checks if the user is viewing the dashboard as a guest
* Returns true if guest mode enabled, and user not logged in
* */
export const isLoggedInAsGuest = () => {
const guestEnabled = isGuestAccessEnabled();
const notLoggedIn = !isLoggedIn();
return guestEnabled && notLoggedIn;
};
/**
* Checks if the current user has admin privileges.
* If no users are setup, then function will always return true
@@ -101,9 +149,10 @@ export const logout = () => {
* @param {String[]} - Array of users
* @returns {Boolean} - True if admin privileges
*/
export const isUserAdmin = (users) => {
if (!users || users.length === 0) return true; // Authentication not setup
if (!isLoggedIn(users)) return false; // Auth setup, but not signed in as a valid user
export const isUserAdmin = () => {
const users = getUsers();
if (users.length === 0) return true; // Authentication not setup
if (!isLoggedIn()) return false; // Auth setup, but not signed in as a valid user
const currentUser = localStorage[localStorageKeys.USERNAME];
let isAdmin = false;
users.forEach((user) => {
@@ -122,11 +171,10 @@ export const isUserAdmin = (users) => {
* Note that if auth is enabled, but not guest access, and user not logged in,
* then they will never be able to view the homepage, so no button needed
*/
export const getUserState = (appConfig) => {
export const getUserState = () => {
const { notConfigured, loggedIn, guestAccess } = userStateEnum; // Numeric enum options
const users = appConfig.auth || []; // Get auth object
if (!isAuthEnabled(users)) return notConfigured; // No auth enabled
if (isLoggedIn(users)) return loggedIn; // User is logged in
if (isGuestAccessEnabled(appConfig)) return guestAccess; // Guest is viewing
if (!isAuthEnabled()) return notConfigured; // No auth enabled
if (isLoggedIn()) return loggedIn; // User is logged in
if (isGuestAccessEnabled()) return guestAccess; // Guest is viewing
return notConfigured;
};

View File

@@ -24,21 +24,25 @@ export default class ConfigAccumulator {
/* App Config */
appConfig() {
let appConfigFile = {};
if (this.conf) {
appConfigFile = this.conf.appConfig || {};
}
// Set app config from file
if (this.conf) appConfigFile = this.conf.appConfig || {};
// Fill in defaults if anything missing
let usersAppConfig = defaultAppConfig;
if (localStorage[localStorageKeys.APP_CONFIG]) {
usersAppConfig = JSON.parse(localStorage[localStorageKeys.APP_CONFIG]);
} else if (appConfigFile !== {}) {
usersAppConfig = appConfigFile;
}
// Some settings have their own local storage keys, apply them here
usersAppConfig.layout = localStorage[localStorageKeys.LAYOUT_ORIENTATION]
|| appConfigFile.layout || defaultLayout;
usersAppConfig.iconSize = localStorage[localStorageKeys.ICON_SIZE]
|| appConfigFile.iconSize || defaultIconSize;
usersAppConfig.language = localStorage[localStorageKeys.LANGUAGE]
|| appConfigFile.language || defaultLanguage;
// Don't let users modify users locally
if (appConfigFile.auth) usersAppConfig.auth = appConfigFile.auth;
// All done, return final appConfig object
return usersAppConfig;
}

View File

@@ -369,6 +369,27 @@
"minimum": 1,
"maximum": 12,
"description": "Number of items per row"
},
"hideForUsers": {
"type": "array",
"description": "Section will be visible to all users, except for those specified in this list",
"items": {
"type": "string",
"description": "Username for the user that will not be able to view this section"
}
},
"showForUsers": {
"type": "array",
"description": "Section will be hidden from all users, except for those specified in this list",
"items": {
"type": "string",
"description": "Username for the user that will have access to this section"
}
},
"hideForGuests": {
"type": "boolean",
"default": false,
"description": "If set to true, section will be visible for logged in users, but not for guests"
}
}
},

View File

@@ -126,7 +126,7 @@ export default {
},
isUserAlreadyLoggedIn() {
const users = this.appConfig.auth;
const loggedIn = (!users || users.length === 0 || isLoggedIn(users));
const loggedIn = (!users || users.length === 0 || isLoggedIn());
return (loggedIn && this.existingUsername);
},
isGuestAccessEnabled() {