Skip to content

Instantly share code, notes, and snippets.

@karantakalkar
Last active August 18, 2020 06:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karantakalkar/ce6c6d3cb3d84a0d5ce43a8d859d92ae to your computer and use it in GitHub Desktop.
Save karantakalkar/ce6c6d3cb3d84a0d5ce43a8d859d92ae to your computer and use it in GitHub Desktop.
has-permission


GSoC'20 Task: Has Permission Directive


Task Description

  1. Create the has-permission directive to enforce permissions according to the logged-in user's role as in community-app.
  2. Illustrate its usage with at least one component in the accounting section of the web-app.

Web App Implementation

  1. Initialize Directives Module
  2. Initialize Has Permission Directive
  3. Get user credentials from local/session storage (depending on whether user chose to 'remember me')
  4. Get user permissions from credentials
  5. Define hasPermissions() function to check if user is permitted.
  6. Check if value passed to directive is a string, Check if value is a negation.
  7. Show the element if user has permission and value passed is not a negation or If user doesn't have permission but value passed is a negation.
  8. There are 3 main permissions 'All functions', 'All functions read' and special permissions (permissions other than former two present in user permissions array).

Unlike Community App I don't think there is need to store permissions in local storage explicitly as they aren't being utilized in any other feature than this.

I have kept the code structure similar to Angular's ngIf structural directive.

Pull Request: openMF/web-app#722

Issue: openMF/web-app#721

Screenshots

Accounting.html with 'All functions' Permission

Screenshot from 2020-04-07 18-34-50

Accounting.html with 'All functions read' Permission only. (Only READ_ features accessible)

Screenshot from 2020-04-07 18-32-58

Accounting.html with special permissions only

Note that none of entries in this page have special permissions, they are all covered in 'All functions' permission, So I had to negate the above two permission grants and replace orignal permission with one of listed special permission (CREATE_REPORT instead of CREATE_JOURNALENTRY) to get the filtering.

Screenshot from 2020-04-07 18-30-54

Community App Implementation

Main.js Community App

// adds user permission as local storage object, not needed as user credentials already contain user permissions
$rootScope.setPermissions = function (permissions) {
                $rootScope.permissionList = permissions;
                localStorageService.addToLocalStorage('userPermissions', permissions);
                $rootScope.$broadcast('permissionsChanged')
            };

// main directive functionallity
$rootScope.hasPermission = function (permission) {
                permission = permission.trim();
                //FYI: getting all permissions from localstorage, because if scope changes permissions array will become undefined
                $rootScope.permissionList = localStorageService.getFromLocalStorage('userPermissions');
                //If user is a Super user return true
                if ($rootScope.permissionList && _.contains($rootScope.permissionList, "ALL_FUNCTIONS")) {
                    return true;
                } else if ($rootScope.permissionList && permission && permission != "") {
                    //If user have all read permission return true
                    if (permission.substring(0, 5) == "READ_" && _.contains($rootScope.permissionList, "ALL_FUNCTIONS_READ")) {
                        return true;
                    } else if (_.contains($rootScope.permissionList, permission)) {
                        //check for the permission if user doesn't have any special permissions
                        return true;
                    } else {
                        //return false if user doesn't have permission
                        return false;
                    }
                } else {
                    //return false if no value assigned to has-permission directive
                    return false;
                }
                ;
            };

// on login set user permissions in local storage, not needed as in web app 
// authentication service login sets user credentials in local/session storage
scope.$on("UserAuthenticationSuccessEvent", function (event, data) {
                scope.authenticationFailed = false;
                scope.resetPassword = data.shouldRenewPassword;
                if (sessionManager.get(data)) {
                    scope.currentSession = sessionManager.get(data);
                    scope.start(scope.currentSession);
                    if (scope.currentSession.user && scope.currentSession.user.userPermissions) {
                        $rootScope.setPermissions(scope.currentSession.user.userPermissions);
                    }
                    location.path('/home').replace();
                } else {
                    scope.loggedInUserId = data.userId;
                }
                ;
            });
// Controls user session and storage, may not be needed.
// currently no such session manager in web app
// sets user permissions on session restore
sessionManager.restore(function (session) {
                scope.currentSession = session;
                scope.start(scope.currentSession);
                if (session.user != null && session.user.userPermissions) {
                    $rootScope.setPermissions(session.user.userPermissions);
                    localStorageService.addToLocalStorage('userPermissions', session.user.userPermissions);
                }
                ;
            });

HasPermissionDirective.js Community App

(function (module) {
    mifosX.directives = _.extend(module, {
        HasPermissionDirective: function ($rootScope) {
            return {
                
                // Throws error if value passed to directive is not a string
                link: function (scope, element, attrs) {
                    if (!_.isString(attrs.hasPermission))
                        throw "hasPermission value must be a string";
                    
                    // trim passed value to remove whitespace
                    var value = attrs.hasPermission.trim();
                    // check for negation
                    var notPermissionFlag = value[0] === '!';
                    if (notPermissionFlag) {
                        value = value.slice(1).trim();
                    }
                    
                    // toggle visibility based on user permissions
                    function toggleVisibilityBasedOnPermission() {
                        var hasPermission = $rootScope.hasPermission(value);

                        if (hasPermission && !notPermissionFlag || !hasPermission && notPermissionFlag)
                            $(element).show();
                        else
                            $(element).hide();
                    }
                    
                    // toggle visibility if user permissions changed.
                    // Not needed.
                    toggleVisibilityBasedOnPermission();
                    scope.$on('permissionsChanged', toggleVisibilityBasedOnPermission);
                }
            };
        }
    });
}(mifosX.directives || {}));

mifosX.ng.application.directive("hasPermission", ['$rootScope', mifosX.directives.HasPermissionDirective]).run(function ($log) {
    $log.info("HasPermissionDirective initialized");
});

Bibliography

https://angular.io/guide/structural-directives

https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_if.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment