Skip to content
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

Implemented: feature for user authorisation (#2ebg32y) #188

Merged
merged 39 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b0c8732
Implmented: logic to set and check authorization for the current user…
adityasharma7 Jun 8, 2022
e99954b
Implemented: common method to set permission for the user (#2ebg32y)
adityasharma7 Jun 9, 2022
2baad0f
Improved: set permission for the user on initial load (#2ebg32y)
adityasharma7 Jun 10, 2022
d48ba17
Removed: unused AppAbility as constant already defined for Subject an…
adityasharma7 Jun 10, 2022
fcf75b5
Implemented: separate method to reset ability and use ACTIONS & SUBJE…
adityasharma7 Jun 10, 2022
a3a873f
Added: comments (#2ebg32y)
adityasharma7 Jun 10, 2022
ab17e8d
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Oct 20, 2022
2f9fe6d
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Oct 27, 2022
25203da
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Nov 2, 2022
63c97a0
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Nov 10, 2022
f202084
Implemented: claim based ability builder and udated permissions based…
adityasharma7 Dec 7, 2022
38a5607
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Dec 7, 2022
8b0969c
Upgrade @casl/vue to 2.2.0 (#2ebg32y)
adityasharma7 Dec 7, 2022
969846d
Implemented: logic to get permission on login and persist on app resu…
adityasharma7 Dec 19, 2022
318054a
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Dec 19, 2022
9d9e840
Implemented: logic to disallow user to navigate to pages without perm…
adityasharma7 Dec 21, 2022
a039962
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Dec 27, 2022
323b97e
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Dec 28, 2022
d4f3557
Improved: handled case for undefined permission on unauthenticated re…
adityasharma7 Dec 28, 2022
e2f8829
Improved: handled case when all the permissions are not received (#2e…
adityasharma7 Dec 29, 2022
484468b
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Jan 2, 2023
5720516
Fixed: error due to missing null check (#2ebg32y)
adityasharma7 Jan 2, 2023
c8edc10
Improved: refactored login action code using guard clauses (#2ebg32y)
adityasharma7 Jan 2, 2023
91dedb7
Improved: comments and set await for get user permission and profile …
adityasharma7 Jan 2, 2023
5d5532d
Implemented: logic to fetch permission based upon rules (#2ebg32y)
adityasharma7 Jan 4, 2023
77fab20
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Jan 9, 2023
2040f71
Improved: fetched app specific essential data in login (#2ebg32y)
adityasharma7 Jan 12, 2023
718d25c
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Jan 20, 2023
50d02ec
Fixed: merge conflicts missed
adityasharma7 Jan 20, 2023
da0bb3a
Improved: code to handle permission configuration as Vue plugin (#2eb…
adityasharma7 Jan 20, 2023
f139892
Improved: applied permission on pipeline and configuration page (#2eb…
adityasharma7 Jan 24, 2023
aa30e09
Implemented: added permissions check on all pages and added rules (#2…
adityasharma7 Jan 25, 2023
83938d3
Improved: allowed user to navigate to configuration page and blocked …
adityasharma7 Jan 25, 2023
ecd2d9e
Added: documentation for the authorisation methods (#2ebg32y)
adityasharma7 Jan 30, 2023
67c8547
Merge branch 'main' of https://github.com/hotwax/job-manager into #2e…
adityasharma7 Jan 30, 2023
36f29e4
Improved: used logger (#2ebg32y)
adityasharma7 Jan 30, 2023
12fef96
Improved: handling of error (#2ebg32y)
adityasharma7 Jan 30, 2023
c64c081
Fixed: removed unused imports (#2ebg32y)
adityasharma7 Jan 30, 2023
e766c54
Fixed: removed unused code (#2ebg32y)
adityasharma7 Jan 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions capacitor.config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"appId": "co.hotwax.ionic.sdk",
"appName": "ionic-sdk",
"appId": "co.hotwax.jobmanager",
"appName": "Job Manager",
"bundledWebRuntime": false,
"npmClient": "npm",
"webDir": "dist",
Expand Down
1,678 changes: 900 additions & 778 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"@capacitor/android": "^2.4.7",
"@capacitor/core": "^2.4.7",
"@capacitor/ios": "^2.4.7",
"@casl/ability": "^6.0.0",
"@casl/vue": "^2.2.0",
"@hotwax/apps-theme": "^1.1.0",
"@hotwax/app-version-info": "^1.0.0",
"@ionic/core": "6.2.9",
Expand All @@ -24,6 +26,7 @@
"@types/papaparse": "^5.3.1",
"axios": "^0.21.4",
"axios-cache-adapter": "^2.7.3",
"boon-js": "^2.0.3",
"core-js": "^3.6.5",
"file-saver": "^2.0.5",
"http-status-codes": "^2.1.4",
Expand Down
7 changes: 3 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ export default defineComponent({
emitter.on('dismissLoader', this.dismissLoader);
emitter.on('playAnimation', this.playAnimation);
// Handles case when user resumes or reloads the app
// Luxon timezzone should be set with the user's selected timezone
if (this.userProfile && this.userProfile.userTimeZone) {
Settings.defaultZone = this.userProfile.userTimeZone;
if (this.userProfile) {
// Luxon timezone should be set with the user's selected timezone
this.userProfile.userTimeZone && (Settings.defaultZone = this.userProfile.userTimeZone);
}
},
unmounted() {
Expand All @@ -98,7 +98,6 @@ export default defineComponent({
},
setup(){
const store = useStore();

return {
store,
}
Expand Down
4 changes: 4 additions & 0 deletions src/authorization/Actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
'APP_JOB_VIEW': 'APP_JOB_VIEW',
'APP_JOB_UPDATE': 'APP_JOB_UPDATE'
}
13 changes: 13 additions & 0 deletions src/authorization/Rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
"APP_PIPELINE_VIEW": "",
"APP_INVENTORY_VIEW": "",
"APP_PRODUCT_VIEW": "",
"APP_PREORDER_VIEW": "",
"APP_ORDERS_VIEW": "",
"APP_JOB_DETAILS_VIEW": "",
"APP_INITIAL_LOAD_VIEW": "",
"APP_MISC_VIEW": "",
"APP_BULK_EDITOR_VIEW": "COMMON_ADMIN",
"APP_JOB_VIEW": "",
"APP_JOB_UPDATE": "COMMON_ADMIN",
} as any
124 changes: 124 additions & 0 deletions src/authorization/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { AbilityBuilder, PureAbility } from '@casl/ability';
import { getEvaluator, parse } from 'boon-js';
import { Tokens } from 'boon-js/lib/types'

// TODO Improve this
// We will move this code to an external plugin and use below Actions and Rules accordlingly
let Actions = {} as any;
let Rules = {} as any;

// We are using CASL library to define permissions.
// Instead of using Action-Subject based authorisation we are going with Claim based Authorization.
// We would be defining the permissions for each action and case, map with server permissiosn based upon certain rules.
// https://casl.js.org/v5/en/cookbook/claim-authorization
// Following the comment of Sergii Stotskyi, author of CASL
// https://github.com/stalniy/casl/issues/525
// We are defining a PureAbility and creating an instance with AbilityBuilder.
type ClaimBasedAbility = PureAbility<string>;
const { build } = new AbilityBuilder<ClaimBasedAbility>(PureAbility);
const ability = build();

/**
* The method returns list of permissions required for the rules. We are having set of rules,
* through which app permissions are defined based upon the server permissions.
* When getting server permissions, as all the permissions are not be required.
* Specific permissions used defining the rules are extracted and sent to server.
* @returns permissions
*/
const getServerPermissionsFromRules = () => {
// Iterate for each rule
const permissions = Object.keys(Rules).reduce((permissions: any, rule: any) => {
const permissionRule = Rules[rule];
// some rules may be empty, no permission is required from server
if (permissionRule) {
// Each rule may have multiple permissions along with operators
// Boon js parse rules into tokens, each token may be operator or server permission
// permissionId will have token name as identifier.
const permissionTokens = parse(permissionRule);
permissions = permissionTokens.reduce((permissions: any, permissionToken: any) => {
// Token object with name as identifier has permissionId
if (Tokens.IDENTIFIER === permissionToken.name) {
permissions.push(permissionToken.value);
}
return permissions;
}, permissions)
}
return permissions;
}, [])
return permissions;
}

/**
* The method is used to prepare app permissions from the server permissions.
* Rules could be defined such that each app permission could be defined based upon certain one or more server permissions.
* @param serverPermissions
* @returns appPermissions
*/
const prepareAppPermissions = (serverPermissions: any) => {
const serverPermissionsInput = serverPermissions.reduce((serverPermissionsInput: any, permission: any) => {
serverPermissionsInput[permission] = true;
return serverPermissionsInput;
}, {})
// Boonjs evaluator needs server permissions as object with permissionId and boolean value
// Each rule is passed to evaluator along with the server permissions
// if the server permissions and rule matches, app permission is added to list
const permissions = Object.keys(Rules).reduce((permissions: any, rule: any) => {
const permissionRule = Rules[rule];
// If for any app permission, we have empty rule we user is assigned the permission
// If rule is not defined, the app permisions is still evaluated or provided to all the users.
if (!permissionRule || (permissionRule && getEvaluator(permissionRule)(serverPermissionsInput))) {
permissions.push(rule);
}
return permissions;
}, [])
const { can, rules } = new AbilityBuilder<ClaimBasedAbility>(PureAbility);
permissions.map((permission: any) => {
can(permission);
})
return rules;
}

/**
*
* Sets the current app permissions. This should be used after perparing the app permissions from the server permissions
* @param permissions
* @returns
*/
const setPermissions = (permissions: any) => {
// If the user has passed undefined or null, it should not break the code
if (!permissions) permissions = [];
ability.update(permissions)
return true;
};

/**
* Resets the permissions list. Used for cases like logout
*/
const resetPermissions = () => setPermissions([]);

/**
*
* @param permission
* @returns
*/
const hasPermission = (permission: string) => ability.can(permission);

export { Actions, getServerPermissionsFromRules, hasPermission, prepareAppPermissions, resetPermissions, setPermissions};

// TODO Move this code to an external plugin, to be used across the apps
export default {
install(app: any, options: any) {

// Rules and Actions could be app and OMS package specific
Rules = options.rules;
Actions = options.actions;

// TODO Check why global properties is not working and apply across.
app.config.globalProperties.$permission = this;
},
getServerPermissionsFromRules,
hasPermission,
prepareAppPermissions,
resetPermissions,
setPermissions
}
7 changes: 5 additions & 2 deletions src/components/InitialJobConfiguration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</ion-item>
</ion-list>

<ion-button size="small" fill="outline" expand="block" @click="runJob('Products')">{{ $t("Run import") }}</ion-button>
<ion-button :disabled="!hasPermission(Actions.APP_JOB_UPDATE)" size="small" fill="outline" expand="block" @click="runJob('Products')">{{ $t("Run import") }}</ion-button>
</section>

<section v-else>
Expand Down Expand Up @@ -93,7 +93,7 @@
</ion-item>
</ion-list>

<ion-button size="small" fill="outline" expand="block" :disabled="!lastShopifyOrderId" @click="runJob('Orders')">{{ $t("Run import") }}</ion-button>
<ion-button size="small" fill="outline" expand="block" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || !lastShopifyOrderId" @click="runJob('Orders')">{{ $t("Run import") }}</ion-button>
</section>
</template>

Expand Down Expand Up @@ -123,6 +123,7 @@ import { mapGetters, useStore } from "vuex";
import { translate } from "@/i18n";
import { DateTime } from 'luxon';
import { handleDateTimeInput,isFutureDate, showToast } from '@/utils';
import { Actions, hasPermission } from '@/authorization'

export default defineComponent({
name: "InitialJobConfiguration",
Expand Down Expand Up @@ -234,6 +235,8 @@ export default defineComponent({
};

return {
Actions,
hasPermission,
calendarClearOutline,
customFulfillmentOptions,
customOrderOptions,
Expand Down
5 changes: 4 additions & 1 deletion src/components/JobActionsPopover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<ion-icon slot="start" :icon="pinOutline" />
{{ $t("Pin job") }}
</ion-item>
<ion-item @click="runJobNow(job)" lines="none" button>
<ion-item :disabled="!hasPermission(Actions.APP_JOB_UPDATE)" @click="runJobNow(job)" lines="none" button>
<ion-icon slot="start" :icon="flashOutline" />
{{ $t("Run now") }}
</ion-item>
Expand All @@ -40,6 +40,7 @@ import JobHistoryModal from '@/components/JobHistoryModal.vue'
import { Plugins } from '@capacitor/core';
import { showToast } from '@/utils'
import emitter from "@/event-bus"
import { Actions, hasPermission } from '@/authorization'

export default defineComponent({
name: "JobActionsPopover",
Expand Down Expand Up @@ -123,8 +124,10 @@ export default defineComponent({
const store = useStore();

return {
Actions,
copyOutline,
flashOutline,
hasPermission,
pinOutline,
store,
timeOutline
Expand Down
17 changes: 10 additions & 7 deletions src/components/JobConfiguration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,26 @@

<div class="actions desktop-only">
<div>
<ion-button size="small" fill="outline" color="medium" :disabled="currentJob.statusId === 'SERVICE_DRAFT'" @click="skipJob(currentJob)">{{ $t("Skip once") }}</ion-button>
<ion-button size="small" fill="outline" color="danger" :disabled="currentJob.statusId === 'SERVICE_DRAFT'" @click="cancelJob(currentJob)">{{ $t("Disable") }}</ion-button>
<ion-button size="small" fill="outline" color="medium" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || currentJob.statusId === 'SERVICE_DRAFT'" @click="skipJob(currentJob)">{{ $t("Skip once") }}</ion-button>
<ion-button size="small" fill="outline" color="danger" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || currentJob.statusId === 'SERVICE_DRAFT'" @click="cancelJob(currentJob)">{{ $t("Disable") }}</ion-button>
</div>
<div>
<ion-button size="small" fill="outline" @click="saveChanges()">{{ $t("Save changes") }}</ion-button>
<ion-button :disabled="!hasPermission(Actions.APP_JOB_UPDATE)" size="small" fill="outline" @click="saveChanges()">{{ $t("Save changes") }}</ion-button>
</div>
</div>

<div class=" actions mobile-only">
<ion-button size="small" expand="block" fill="outline" color="medium" :disabled="status === 'SERVICE_DRAFT'" @click="skipJob(currentJob)">{{ $t("Skip once") }}</ion-button>
<ion-button size="small" expand="block" fill="outline" color="danger" :disabled="status === 'SERVICE_DRAFT'" @click="cancelJob(currentJob)">{{ $t("Disable") }}</ion-button>
<ion-button expand="block" @click="saveChanges()">{{ $t("Save changes") }}</ion-button>
<ion-button size="small" expand="block" fill="outline" color="medium" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || status === 'SERVICE_DRAFT'" @click="skipJob(currentJob)">{{ $t("Skip once") }}</ion-button>
<ion-button size="small" expand="block" fill="outline" color="danger" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || status === 'SERVICE_DRAFT'" @click="cancelJob(currentJob)">{{ $t("Disable") }}</ion-button>
<ion-button :disabled="!hasPermission(Actions.APP_JOB_UPDATE)" expand="block" @click="saveChanges()">{{ $t("Save changes") }}</ion-button>
</div>
</section>
<div class="more-actions">
<ion-item @click="viewJobHistory(currentJob)" button>
<ion-icon slot="start" :icon="timeOutline" />
{{ $t("History") }}
</ion-item>
<ion-item @click="runNow(title, currentJob)" button>
<ion-item :disabled="!hasPermission(Actions.APP_JOB_UPDATE)" @click="runNow(title, currentJob)" button>
<ion-icon slot="start" :icon="flashOutline" />
{{ $t("Run now") }}
</ion-item>
Expand Down Expand Up @@ -129,6 +129,7 @@ import { DateTime } from 'luxon';
import { translate } from '@/i18n'
import { useRouter } from "vue-router";
import emitter from '@/event-bus';
import { Actions, hasPermission } from '@/authorization'

export default defineComponent({
name: "JobConfiguration",
Expand Down Expand Up @@ -399,9 +400,11 @@ export default defineComponent({
const router = useRouter();

return {
Actions,
calendarClearOutline,
copyOutline,
flashOutline,
hasPermission,
timeOutline,
timerOutline,
store,
Expand Down
Loading