Yearly Archives: 2016

Release versioning of web application in Bamboo build

Automating the build and deploy workflow in Atlassian Bamboo is a useful practice to streamline the delivery of a web application. In this post I’m sharing a method to do automatic release versioning of a Javascript web application. With this method you can declare the application version in just one place and have Bamboo assign it to each build, along with the build number, such as:

2.0.0.85
2.0.0.86
...

The first thing to think about is where to store the application version. In the case of a project with NodeJS dependencies, we already have a package.json file in the project root directory where we can store the current version:

{
    "name": "My Application",
    "author": "John Doe",
    "version": "2.0.0",
     "devDependencies": {
         "bower": "*",
         "grunt": "^0.4.0"
     },
     "engines": {
         "node": ">=0.10.0"
     }
}

The next step is having Bamboo read the current version from package.json during the build process. To do that, we can leverage the Inject Bamboo variables task, which reads variables from a text file in “key=value” format.  So we first have to extract the version from package.json and write it to a text file. We can do this by adding a new Script task to the build job. The inline shell script we’ll write will use Node to read the version from package.json, append the Bamboo build number and write it to a text file with the expected format:

app_version="$(node -e 'console.log(require("./package.json").version);')"
version="${app_version}.${bamboo_buildNumber}"
echo "version=$version" > variables.txt

After the script task we can now add the Inject Bamboo variables task:

Bamboo Inject Variables from file task

The version variable will be stored in the bamboo.artifact namespace, and we can access it this way:

${bamboo.artifact.version}

The last step is to configure the release versioning of the deployment plan to use the custom generated version:

Bamboo custom release versioning

 

Developing a permission-based authorization system in a AngularJS app

In this post I’m going to show an implementation of a simple authentication and authorization system in a AngularJS web application. More in detail, the solution provides a declarative way to restrict access to both views and page content. Accessing user information and permissions from controllers and templates is also made really simple. This solution uses a service, a directive and session storage to implement a permission-based access control system.

The solution expects a back-end providing user profile data and permissions in this simple format:

{
   name: "John Doe",
   permissions: ['list_orders', 'read_statistics']
 // ...
}

Restricting access to views

Access control on views is set up on the $routeProvider configuration, so that you can annotate each route with two attributes: requiresAuthentication and permissions. The first is a boolean indicating if the user has to be logged in to access the view. The permissions element is a string array containing the requested permissions in a disjunctive (OR) fashion, so that the user is authorized to access the route if he owns at least one of the permissions.

angular
  .module('app', [
    'ngResource',
    'ngRoute',
    'AuthServices'
  ])
  .config(function ($routeProvider) {
    $routeProvider
      .when('/login', {
        templateUrl: 'views/login.html',
        controller: 'LoginCtrl'
      })
      .when('/home', {
        templateUrl: 'views/home.html',
        controller: 'HomeCtrl',
        requiresAuthentication: true
      })
      .when('/sales/orders/new', {
        templateUrl: 'views/sales/new_order.html',
        controller: 'OrderDetailCtrl',
        requiresAuthentication: true,
        permissions: ["administration"]
      })
      .when('/sales/orders', {
        templateUrl: 'views/sales/orders.html',
        controller: 'OrdersCtrl',
        requiresAuthentication: true,
        permissions: ["administration", "list_orders"]
      })
 });

Here we have a login view with public access, a home view accessible by all authenticated users, a new order view accessible only by users having the “administration” permission, and a order list view accessible by all users having an “administration” or “list_orders” permission.

Restricting access to page content

Another use case of the system is the access control on the page content. To show an element only to users having a particular permission, just use the permission directive on the DOM element. In this example, we have a navigation bar where we want to show only links to sections the user is authorized to access. As in the routes case, the permission attribute value is an array of permissions, so that the binded DOM element will be displayed only if the active user has at least one of them.

<div class="sidebar" ng-show="user" ng-cloak>
   <ul class="navigation">

      <li permission="['administration']">
         <span>Administration area</span>
         <ul>
           <li><a href="#/users">Users</a></li>
           <li><a href="#/settings">Settings</a></li>
         </ul>
      </li>

      <li permission="['administration', 'list_orders']">
         <span>Sales</span>
         <ul>
           <li permission="['administration']">
             <a href="#/sales/orders/new">New order</a>
           </li>
           <li permission="['administration', 'list_orders']">
              <a href="#/sales/orders">Orders list</a>
           </li>
         </ul>
      </li>

   </ul>

The Auth service and the Permission directive

The Auth service is the main component of the system, implementing the various functionalities. The user profile is saved in the session storage (wrapped by the ngStorage module) after login. A reference to the current user is also added to the root scope of the app automatically, to make it easy referencing it from the templates.

angular.module('AuthServices', ['ngResource', 'ngStorage'])
.factory('Auth', function($resource, $rootScope, $sessionStorage, $q){
    
    /**
     *  User profile resource
     */
    var Profile = $resource('/api/profile', {}, {
        login: {
            method: "POST",
            isArray : false
        }
    });
    
    var auth = {};
    
    /**
     *  Saves the current user in the root scope
     *  Call this in the app run() method
     */
    auth.init = function(){
        if (auth.isLoggedIn()){
            $rootScope.user = auth.currentUser();
        }
    };
        
    auth.login = function(username, password){
        return $q(function(resolve, reject){
            Profile.login({username:username, password:password}).$promise
            .then(function(data) {                        
                $sessionStorage.user = data;    
                $rootScope.user = $sessionStorage.user;
                resolve();
            }, function() {
                reject();
            });
        });
    };
    

    auth.logout = function() {
        delete $sessionStorage.user;
        delete $rootScope.user;
    };
    
    
    auth.checkPermissionForView = function(view) {
        if (!view.requiresAuthentication) {
            return true;
        }
        
        return userHasPermissionForView(view);
    };
    
    
    var userHasPermissionForView = function(view){
        if(!auth.isLoggedIn()){
            return false;
        }
        
        if(!view.permissions || !view.permissions.length){
            return true;
        }
        
        return auth.userHasPermission(view.permissions);
    };
    
    
    auth.userHasPermission = function(permissions){
        if(!auth.isLoggedIn()){
            return false;
        }
        
        var found = false;
        angular.forEach(permissions, function(permission, index){
            if ($sessionStorage.user.user_permissions.indexOf(permission) >= 0){
                found = true;
                return;
            }                        
        });
        
        return found;
    };
    
    
    auth.currentUser = function(){
        return $sessionStorage.user;
    };
    
    
    auth.isLoggedIn = function(){
        return $sessionStorage.user != null;
    };
    

    return auth;
});

The permission directive can be bound to DOM elements and directly references the Auth service:

angular.module('app')   
.directive('permission', ['Auth', function(Auth) {
   return {
       restrict: 'A',
       scope: {
          permission: '='
       },

       link: function (scope, elem, attrs) {
            scope.$watch(Auth.isLoggedIn, function() {
                if (Auth.userHasPermission(scope.permission)) {
                    elem.show();
                } else {
                    elem.hide();
                }
            });                
       }
   }
}]);

 

Accessing current user data from controllers and templates

Accessing the user data and permissions is quite simple, like in this controller:

angular.module('app')
  .controller('OrdersCtrl', function ($scope, Auth, Sales) {

    if (Auth.userHasPermission(["administration"])){
        // some evil logic here
        var userName = Auth.currentUser().name;
        // ...
    }

});

All you have to do is to add a dependency on the Auth service. To access the user data in a template, simply reference user in root scope:

<div ng-show="user" ng-cloak>
   <span>{{user.full_name}}</span> <a ng-click="logout()">Logout</a>
</div>

 

Putting things together

Access control on views is implemented by listening on route change events. Another thing to recall is that the root scope of the application is reset when the page gets refreshed (e.g., after hitting F5). We handle both this issues in the app run method.

angular.module('app', [
    'ngResource',
    'ngRoute',
    'AuthServices'
])
.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
    Auth.init();
    
    $rootScope.$on('$routeChangeStart', function (event, next) {
        if (!Auth.checkPermissionForView(next)){
            event.preventDefault();
            $location.path("/login");
        }
    });
  }]);

By calling Auth.init here we create again a reference to the user data in the root scope.

Logging in and out

A login controller using the Auth service:

angular.module('app').controller('LoginCtrl', function($scope, $location, Auth) {

    $scope.email = "";
    $scope.password = "";
    $scope.failed = false;

    $scope.login = function() {
        Auth.login($scope.email, $scope.password)
          .then(function() {
              $location.path("/home");
          }, function() {
              $scope.failed = true;
          });
    };

});

Handling logout in the main application controller:

angular.module('app')
  .controller('MainCtrl', function ($scope, $rootScope, $location, Auth) {
      
      $rootScope.logout = function(){
        Auth.logout();
        $location.path("/login");
      };
      
  });

Conclusions

The proposed system is a prototype solution for handling authentication and authorization in a AngularJS app with a concept of user permissions. To implement this in your application, however, there are some back-end-specific details regarding the authentication that might have to be tuned to fit your own architecture.