diff --git a/Gemfile b/Gemfile index d0ff3b0..7c3bafb 100644 --- a/Gemfile +++ b/Gemfile @@ -50,3 +50,5 @@ gem 'spring', group: :development # Use debugger # gem 'debugger', group: [:development, :test] +# Use Pundit for authorization +gem 'pundit', '1.0.0' \ No newline at end of file diff --git a/README.API.Authentication.rdoc b/README.API.Authentication.rdoc index 1a16df1..6a12db6 100644 --- a/README.API.Authentication.rdoc +++ b/README.API.Authentication.rdoc @@ -39,6 +39,89 @@ specified, or if it is incorrect: HTTP Token: Access denied. +== Roles and Permissions == + +The API uses a role-based mechanism for determining what operations a user may perform. + +The rules: + +* Each user has exactly one role, e.g. moderator or administrator. +* Each role has one or more permissions. +* Each role has a level and roles inherit permissions from the roles with a lower level. +* Access to an API operation is granted based on the permissions of to the user's role. + +Technically, a role is expressed as a "numeric level". That "role level number" is then mapped to a descriptive name. +Permissions are then granted based on the "role level". + +Sample roles and permissions: + +* The moderator role has level 10. +* The administrator role has level 20. +* The permission to edit activities requires level 10. +* The permission to delete activities requires level 15. + +Effects of sample roles and permissions: + +* Administrators may edit activities (20 >= 10) +* Moderators may edit activities (10 >= 10) +* Administrators may delete activities (20 >= 15) +* Moderators may NOT delete activities (10 < 15) + +=== Show all roles and permissions === + +Clients may retrieve a list of all roles and permissions available in the system. + +Each role is associated with a level (an integer), which is used to determine a role's different permissions. A role has +all the permissions with a level less than or equal to the role's level. + +In the example below: + +* the administrator role (level 20) can do everything since its no permission requires a higher level than 20. +* the moderator role (level 10) can do everything but edit users (which requires level 20). +* levels can be negative + +Request + + GET http://127.0.0.1:3000/api/v1/system/roles HTTP/1.1 + Authorization: Token token="664a2072e2" + +Response + + { + "permission_levels": { + "system_message_read": -1, + "comment_create": 0, + "activity_create": 0, + "activity_edit_own": 0, + "activity_edit": 10, + "users_edit": 20 + }, + "role_levels": { + "limited_user": -1, + "user": 0, + "moderator": 10, + "administrator": 20 + } + } + +=== Assign role === + +If a user is permitted to edit other users, the user may only grant other users his/her own role, or a lesser role. +Simply put, a moderator can not make himself an administrator but can technically reduce himself to a regular user. + +Roles are assigned by updating the user object (there is no dedicated operation for assigning roles). See below. + +=== Attempting to perform operation which user is not authorized to perform === + +The API will return a 403 Forbidden if a user tries to perform an operation which he/she does not have permission to +perform, as determined by the user's role. + + HTTP/1.1 403 Forbidden + { + "error":"You are not authorized", + "details":"Your role does not grant you permission to this operation" + } + == Show current user's profile Request @@ -51,12 +134,119 @@ Response HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 { - "email": "USER@HOST", - "display_name": "Alice" - "created_at": "2014-09-27T13:50:55.203Z", - "keys": [{"key": "4d7a9ae16d0092c35f63"}] + "email": "alice@example.com", + "display_name": "Alice", + "created_at": "2014-09-27T15:16:55.416Z", + "role": "user", + "role_permissions": [ + "activity_create", + "activity_edit_own", + "auth_profile_edit", + "comment_create", + "comment_edit_own", + "mediaitem_create_ownactivity", + "mediaitem_edit_own", + "reference_create_ownactivity", + "reference_edit_own", + "system_message_read" + ], + "keys": [{"key": "664a2072e2"}] + } + +== Search for users == + +Search for users by GETting the /users resource. Since there may be thousands of users in the system, it is possible to +filter based on name, e-mail address and role. + +Request for all users: + + GET http://127.0.0.1:3000/api/v1/users HTTP/1.1 + Authorization: Token token="664a2072e2" + +Request for users whose name contains 'ali': + + GET http://127.0.0.1:3000/api/v1/users?display_name=ali HTTP/1.1 + Authorization: Token token="664a2072e2" + +Request for users with e-mail addresses containing '@example.com': + + GET http://127.0.0.1:3000/api/v1/users?email=%40example.com HTTP/1.1 + Authorization: Token token="664a2072e2" + +Request for users with the role 'moderator': + + GET http://127.0.0.1:3000/api/v1/users?display_name=moderator HTTP/1.1 + Authorization: Token token="664a2072e2" + +Request for moderators named Alice: + + GET http://127.0.0.1:3000/api/v1/users?email=alice&role=moderator HTTP/1.1 + Authorization: Token token="664a2072e2" + +Response example: + + [ + { + "id": 1, + "email": "alice@example.com", + "email_verified": null, + "display_name": "Alice", + "created_at": "2014-09-27T13:50:55.203Z", + "updated_at": "2014-09-27T13:50:55.203Z", + "role": "user" + }, + ... + ] + +== Display single user == + +Request: + + GET http://127.0.0.1:3000/api/v1/users/1 HTTP/1.1 + Authorization: Token token="664a2072e2" + +Response: + + { + "id": 1, + "email": "alice@example.com", + "email_verified": null, + "display_name": "Alice", + "created_at": "2014-09-27T13:50:55.203Z", + "updated_at": "2014-09-27T13:50:55.203Z", + "role": "user" + } + +== Update user == + +The role of the updated user is automatically reduced to the API user's role, in case API user tries to give the updated +user more permission than granted to the API user. + +Request + + PUT http://127.0.0.1:3000/api/v1/users/1 HTTP/1.1 + Content-Type: application/json + Authorization: Token token="664a2072e2" + { + "email": "alice@example.com", + "display_name": "Alice", + "role": "user" } +Response + + HTTP/1.1 204 No Content + +== Create new user == + +Users can not be explicitly created. + +A new user is automatically created when a Google user provides a Google authentication token for the first time. + +== Delete user == + +Currently not supported. + == Show all users and their API keys (only in development mode) Request @@ -70,7 +260,7 @@ Response (only when server is in development mode) [ { - "email": "USER@HOST", + "email": "alice@example.com", "display_name": "Alice" "keys": [{"key": "4d7a9ae16d0092c35f63"}] }, diff --git a/app/controllers/api/v1/activities_controller.rb b/app/controllers/api/v1/activities_controller.rb index 31fade0..c0b00f9 100644 --- a/app/controllers/api/v1/activities_controller.rb +++ b/app/controllers/api/v1/activities_controller.rb @@ -13,6 +13,7 @@ class ActivitiesController < ApplicationController ACTIVITY_FAVOURITES_STATS_SQL = FavouriteActivity.select('activity_id, count(*) favourite_count').group(:activity_id).to_sql def index + authorize Activity query_conditions = get_find_condition_params if @userApiKey.nil? && query_conditions.has_key?('my_favourites') @@ -292,11 +293,13 @@ def get_activity_ids(activity_versions) end def show + authorize @activity @all_versions = params.has_key?('all_versions') && params[:all_versions] == 'true' respond_with @activity end def create + authorize Activity @activity = Activity.new(status: Db::ActivityVersionStatus::PUBLISHED) @activity.user = @userApiKey.user @@ -341,6 +344,7 @@ def get_or_create_media_file(uri) end def update + authorize @activity # Update non-versioned attributes # Create new revision # Set status of new revision to status of current revision @@ -387,6 +391,7 @@ def update end def destroy + authorize @activity @activity.activity_versions.each { |v| v.categories.clear v.references.clear diff --git a/app/controllers/api/v1/categories_controller.rb b/app/controllers/api/v1/categories_controller.rb index 0983b17..9513acb 100644 --- a/app/controllers/api/v1/categories_controller.rb +++ b/app/controllers/api/v1/categories_controller.rb @@ -8,6 +8,7 @@ class CategoriesController < ApplicationController before_action :load_usage_count, only: [:index, :show] def index + authorize Category @categories = Category.all.includes(:media_file).order(:group => :asc, :name => :asc) respond_with @categories end @@ -21,6 +22,7 @@ def load_usage_count end def create + authorize Category @category = Category.new(validated_params) @category.user = @userApiKey.user @category.media_file = get_or_create_media_file() @@ -32,10 +34,12 @@ def create end def show + authorize @category respond_with @category end def update + authorize @category @category.media_file = get_or_create_media_file() if @category.update(validated_params) head :no_content @@ -59,6 +63,7 @@ def get_or_create_media_file end def destroy + authorize @category if @category.destroy head :no_content else diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index fcc9123..9cf4180 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -4,11 +4,13 @@ class FavouritesController < ApplicationController before_filter :restrict_access_to_api_users def index + authorize FavouriteActivity favourites = @userApiKey.user.favourite_activities.pluck(:activity_id) respond_with favourites end def update + authorize FavouriteActivity user = @userApiKey.user # Start be deleting the user's current favourites... diff --git a/app/controllers/api/v1/media_files_controller.rb b/app/controllers/api/v1/media_files_controller.rb index 7a94b61..c3ba9ce 100644 --- a/app/controllers/api/v1/media_files_controller.rb +++ b/app/controllers/api/v1/media_files_controller.rb @@ -5,11 +5,13 @@ class MediaFilesController < ApplicationController before_action :set_media_file, only: [:show, :update, :destroy, :handle_resized_image_request] def index + authorize MediaFile @media_files = MediaFile.all respond_with @media_files end def create + authorize MediaFile @media_file = MediaFile.new(validated_params) if @media_file.save respond_with :api, :v1, @media_file, status: :created @@ -19,10 +21,12 @@ def create end def show + authorize @media_file respond_with @media_file end def update + authorize @media_file if @media_file.update(validated_params) head :no_content else @@ -31,6 +35,7 @@ def update end def destroy + authorize @media_file if @media_file.destroy head :no_content else @@ -39,6 +44,7 @@ def destroy end def handle_resized_image_request + skip_authorization begin size = params.require(:size) !!Float(size) # Will raise exception if parameter value is not numeric diff --git a/app/controllers/api/v1/ratings_controller.rb b/app/controllers/api/v1/ratings_controller.rb index f2c543f..66e9341 100644 --- a/app/controllers/api/v1/ratings_controller.rb +++ b/app/controllers/api/v1/ratings_controller.rb @@ -7,7 +7,7 @@ class RatingsController < ApplicationController #before_action :load_rating_entity def create - + authorize Rating Rating.delete_all( { :user => @userApiKey.user, @@ -26,6 +26,7 @@ def create end def show + authorize @rating @rating = Rating.find_by!( { :user => @userApiKey.user, @@ -35,6 +36,7 @@ def show end def destroy + authorize @rating result = Rating.delete_all( { :user => @userApiKey.user, diff --git a/app/controllers/api/v1/references_controller.rb b/app/controllers/api/v1/references_controller.rb index cf6b074..8378b63 100644 --- a/app/controllers/api/v1/references_controller.rb +++ b/app/controllers/api/v1/references_controller.rb @@ -4,11 +4,13 @@ class ReferencesController < ApplicationController before_action :set_reference, only: [:show, :update, :destroy] def index + authorize Reference @references = Reference.all respond_with @references end def create + authorize Reference @reference = Reference.new(validated_params) if @reference.save respond_with :api, :v1, @reference, status: :created @@ -18,10 +20,12 @@ def create end def show + authorize @reference respond_with @reference end def update + authorize @reference if @reference.update(validated_params) head :no_content else @@ -30,6 +34,7 @@ def update end def destroy + authorize @reference if @reference.destroy head :no_content else diff --git a/app/controllers/api/v1/related_activities_controller.rb b/app/controllers/api/v1/related_activities_controller.rb index c9f5b96..73ff96b 100644 --- a/app/controllers/api/v1/related_activities_controller.rb +++ b/app/controllers/api/v1/related_activities_controller.rb @@ -7,6 +7,7 @@ class RelatedActivitiesController < ApplicationController before_action :set_related_activity, only: [:destroy] def index + authorize ActivityRelation @related_activities = ActivityRelation.where(:activity_id => params[:activity_id]) respond_with @related_activities end @@ -14,6 +15,9 @@ def index def create @related_activity = ActivityRelation.new(validated_params) @related_activity.user = @userApiKey.user + + authorize @related_activity + if @related_activity.save respond_with :api, :v1, @related_activity, status: :created else @@ -22,6 +26,7 @@ def create end def destroy + authorize @related_activity if @related_activity.destroy head :no_content else @@ -30,6 +35,7 @@ def destroy end def set_auto_generated + authorize ActivityRelation #activity = Activity.find(params[:activity_id]) ActivityRelation.delete_all({ :is_auto_generated => true, diff --git a/app/controllers/api/v1/system_controller.rb b/app/controllers/api/v1/system_controller.rb index d209caf..2d64cd0 100644 --- a/app/controllers/api/v1/system_controller.rb +++ b/app/controllers/api/v1/system_controller.rb @@ -4,8 +4,15 @@ class SystemController < ApplicationController before_filter :restrict_access_to_api_users, except: [:ping] def ping + authorize :system head :no_content end + + def roles + authorize :system + roles = { permission_levels: ApplicationPolicy::PERMISSIONS, role_levels: User.roles } + respond_with roles + end end end end diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb index 31042cb..f174c66 100644 --- a/app/controllers/api/v1/users_controller.rb +++ b/app/controllers/api/v1/users_controller.rb @@ -1,15 +1,23 @@ module Api module V1 class UsersController < ApplicationController - before_filter :restrict_access_to_api_users, except: [:all_api_keys] + before_filter :restrict_access_to_api_users before_action :set_user, only: [:show, :update, :destroy] - #def index - # @users = User.all - # respond_with @users - #end + def index + authorize User + p = validated_params + + users = User + users = users.where("LOWER(display_name) LIKE ?", "%#{p[:display_name].mb_chars.downcase.to_s}%") if p.has_key?('display_name') + users = users.where("LOWER(email) LIKE ?", "%#{p[:email].mb_chars.downcase.to_s}%") if p.has_key?('email') + users = users.where(role: p[:role]) if p.has_key?('role') + + respond_with users + end def create + authorize User @user = User.new(validated_params) apiKey = UserApiKey.new() apiKey.user = @user @@ -23,10 +31,12 @@ def create end def profile + authorize User respond_with @userApiKey.user end def all_api_keys + authorize User if Rails.env.development? @users = User.all respond_with @users @@ -35,19 +45,22 @@ def all_api_keys end end - #def show - # respond_with @user - #end + def show + authorize @user + respond_with @user + end - #def update - # if @user.update(validated_params) - # head :no_content - # else - # respond_with @user.errors, status: :unprocessable_entity - # end - #end + def update + authorize @user + if @user.update(validated_params) + head :no_content + else + respond_with @user.errors, status: :unprocessable_entity + end + end def update_profile + authorize @userApiKey.user if @userApiKey.user.update(validated_params) head :no_content else @@ -65,17 +78,23 @@ def update_profile private - #def find_user(id) - # User.find(id) - #end + def find_user(id) + User.find(id) + end - #def set_user - # @user = find_user(params[:id]) - #end + def set_user + @user = find_user(params[:id]) + end def validated_params Rails.logger.info("PARAMS: #{params.inspect}") - params.permit(:display_name, :email) + p = params.permit(:display_name, :email, :role) + + # If user has specified a role, make sure the role doesn't have a higher level than the user's current role. + # Incorrect role names are ignored at this point but will later be rejected by ActiveRecord. + p[:role] = [User.roles[p[:role]], User.roles[@userApiKey.user.role]].min unless (p[:role].nil? || !User.roles.keys.include?(p[:role])) + + p end end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 491a07c..c2794e5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,10 +1,16 @@ require 'google-id-token' class ApplicationController < ActionController::Base + include Pundit # Pundit is the authorization module + respond_to :json + + after_action :verify_authorized # Make sure that all controller actions invoke either authorize or skip_authorization. + rescue_from ActiveRecord::RecordNotUnique, :with => :error_record_not_unique rescue_from ActiveRecord::RecordNotFound, :with => :error_record_not_found rescue_from ActiveRecord::RecordInvalid, :with => :error_record_has_invalid_data + rescue_from Pundit::NotAuthorizedError, :with => :error_unauthorized_role # Print short error message, instead of default stack trace in HTML format, when user is not authorized to invoke an operation. # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. @@ -22,6 +28,11 @@ class ApplicationController < ActionController::Base HTTP_HEADER_APIKEY = 'X-ScoutAPI-APIKey' + # The current_user method is required by Pundit + def current_user + @userApiKey.user + end + def restrict_access_to_api_users_if_credentials_supplied restrict_access_to_api_users if request.authorization end @@ -74,11 +85,11 @@ def restrict_access_to_api_users end else Rails.logger.error('Invalid Google ID token') - error_forbidden('Invalid Google ID token') + error_unauthorized('Invalid Google ID token') end rescue JWT::ExpiredSignature Rails.logger.error('Signature has expired') - error_forbidden('Signature has expired') + error_unauthorized('Signature has expired') end else Rails.logger.error('Unsupported token type') @@ -94,7 +105,11 @@ def error_forbidden(error) end def error_unauthorized(error) - render_error error, 'You must provide credentials in order to perform the search/operation', :unauthorized + render_error error, 'You must provide credentials', :unauthorized + end + + def error_unauthorized_role + render_error 'Your role does not grant you permission to this operation', 'You are not authorized', :forbidden end def error_record_not_unique(error) diff --git a/app/models/user.rb b/app/models/user.rb index fb7f98e..d9d227c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,9 @@ class User < ActiveRecord::Base + # The different roles and their "levels". The values should match those used in ApplicationPolicy::PERMISSIONS. + enum role: {limited_user: -1, user: 0, moderator: 10, administrator: 20} + + after_initialize :set_default_role, :if => :new_record? + has_many :user_identities, :dependent => :delete_all has_many :user_api_keys, :dependent => :delete_all has_many :activities @@ -8,4 +13,13 @@ class User < ActiveRecord::Base has_many :ratings, :dependent => :delete_all has_many :activity_versions has_many :activity_relations + + # Return sorted list of permissions which user has based on their role + def role_permissions + ApplicationPolicy::PERMISSIONS.select { |k, v| v <= User.roles[role] }.keys.sort + end + + def set_default_role + self.role ||= :user + end end diff --git a/app/policies/activity_policy.rb b/app/policies/activity_policy.rb new file mode 100644 index 0000000..641579e --- /dev/null +++ b/app/policies/activity_policy.rb @@ -0,0 +1,22 @@ +class ActivityPolicy < ApplicationPolicy + + def index? + true + end + + def update? + User.roles[user.role] >= PERMISSIONS[:activity_edit] || (record.user == user && User.roles[user.role] >= PERMISSIONS[:activity_edit_own]) + end + + def show? + true + end + + def create? + User.roles[user.role] >= PERMISSIONS[:activity_create] + end + + def destroy? + update? + end +end \ No newline at end of file diff --git a/app/policies/activity_relation_policy.rb b/app/policies/activity_relation_policy.rb new file mode 100644 index 0000000..499abd7 --- /dev/null +++ b/app/policies/activity_relation_policy.rb @@ -0,0 +1,18 @@ +class ActivityRelationPolicy < ApplicationPolicy + + def index? + true + end + + def create? + User.roles[user.role] >= PERMISSIONS[:activity_edit] || (record.related_activity.user == user && User.roles[user.role] >= PERMISSIONS[:activity_edit_own]) + end + + def destroy? + update? + end + + def set_auto_generated? + User.roles[user.role] >= PERMISSIONS[:activity_edit] + end +end \ No newline at end of file diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb new file mode 100644 index 0000000..2d9a2d6 --- /dev/null +++ b/app/policies/application_policy.rb @@ -0,0 +1,114 @@ +class ApplicationPolicy + attr_reader :user, :record + + PERMISSIONS = { + # + # Permissions for all users (level <0): + # + + system_message_read: -100, + + # + # Permissions for regular users (level 0): + # + + comment_create: 0, + + activity_create: 0, + activity_edit_own: 0, + comment_edit_own: 0, + auth_profile_edit: 0, + + # Permission to set personal rating for any activity. NOT the same as changing other users' ratings. + rating_set_own: 0, + + # Create media items, e.g. by uploading a photo, associated with activities created by same user + #mediaitem_create_ownactivity: 0, + mediaitem_edit_own: 0, + + # Create media items, e.g. by uploading a photo, associated with activities created by same user + #reference_create_ownactivity: 0, + reference_edit_own: 0, + + # + # Permissions for moderators (level 10): + # + + activity_edit: 10, + #activity_edit_withoutreview: 10, + comment_edit: 10, + #comment_create_withoutreview: 10, + category_create: 10, + category_edit: 10, + + # Create media items, e.g. by uploading a photo, associated with any activity + mediaitem_create: 10, + reference_create: 10, + + # Assign user's role (or lesser) to any user with a role lesser than the user. + auth_role_assignown: 10, + + # + # Permissions for administrators (level 20): + # + + system_message_create: 20, + system_message_edit: 20, + auth_role_assign: 20, + auth_role_list: 20, + auth_user_edit: 20, + auth_user_create: 20, + mediaitem_edit: 20, + reference_edit: 20 + } + + def initialize(user, record) + @user = user + @record = record + end + + def index? + false + end + + def show? + scope.where(:id => record.id).exists? + end + + def create? + false + end + + def new? + create? + end + + def update? + false + end + + def edit? + update? + end + + def destroy? + false + end + + def scope + Pundit.policy_scope!(user, record.class) + end + + class Scope + attr_reader :user, :scope + + def initialize(user, scope) + @user = user + @scope = scope + end + + def resolve + scope + end + end +end diff --git a/app/policies/category_policy.rb b/app/policies/category_policy.rb new file mode 100644 index 0000000..65bfbe7 --- /dev/null +++ b/app/policies/category_policy.rb @@ -0,0 +1,22 @@ +class CategoryPolicy < ApplicationPolicy + + def index? + true + end + + def update? + User.roles[user.role] >= PERMISSIONS[:category_edit] + end + + def show? + true + end + + def create? + User.roles[user.role] >= PERMISSIONS[:category_create] + end + + def destroy? + update? + end +end \ No newline at end of file diff --git a/app/policies/favourite_policy.rb b/app/policies/favourite_policy.rb new file mode 100644 index 0000000..d2ad870 --- /dev/null +++ b/app/policies/favourite_policy.rb @@ -0,0 +1,13 @@ +class FavouritePolicy < ApplicationPolicy + + # No special permission needed to view personal favourites + def index? + true + end + + # No special permission needed to view personal favourites + def update? + index? + end + +end \ No newline at end of file diff --git a/app/policies/media_file_policy.rb b/app/policies/media_file_policy.rb new file mode 100644 index 0000000..94f271c --- /dev/null +++ b/app/policies/media_file_policy.rb @@ -0,0 +1,22 @@ +class MediaFilePolicy < ApplicationPolicy + + def index? + true + end + + def update? + User.roles[user.role] >= PERMISSIONS[:mediaitem_edit] || (record.user == user && User.roles[user.role] >= PERMISSIONS[:mediaitem_edit_own]) + end + + def show? + true + end + + def create? + User.roles[user.role] >= PERMISSIONS[:mediaitem_create] + end + + def destroy? + update? + end +end \ No newline at end of file diff --git a/app/policies/rating_policy.rb b/app/policies/rating_policy.rb new file mode 100644 index 0000000..2246df6 --- /dev/null +++ b/app/policies/rating_policy.rb @@ -0,0 +1,14 @@ +class RatingPolicy < ApplicationPolicy + + def show? + User.roles[user.role] >= PERMISSIONS[:rating_set_own] + end + + def create? + show? + end + + def destroy? + show? + end +end \ No newline at end of file diff --git a/app/policies/reference_policy.rb b/app/policies/reference_policy.rb new file mode 100644 index 0000000..e338962 --- /dev/null +++ b/app/policies/reference_policy.rb @@ -0,0 +1,22 @@ +class ReferencePolicy < ApplicationPolicy + + def index? + true + end + + def update? + User.roles[user.role] >= PERMISSIONS[:reference_edit] || (record.user == user && User.roles[user.role] >= PERMISSIONS[:reference_edit_own]) + end + + def show? + true + end + + def create? + User.roles[user.role] >= PERMISSIONS[:reference_create] + end + + def destroy? + update? + end +end \ No newline at end of file diff --git a/app/policies/system_policy.rb b/app/policies/system_policy.rb new file mode 100644 index 0000000..ef21f7f --- /dev/null +++ b/app/policies/system_policy.rb @@ -0,0 +1,11 @@ +class SystemPolicy < Struct.new(:user, :system) + + def ping? + true + end + + def roles? + User.roles[user.role] >= ApplicationPolicy::PERMISSIONS[:auth_role_list] + end + +end \ No newline at end of file diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb new file mode 100644 index 0000000..a813231 --- /dev/null +++ b/app/policies/user_policy.rb @@ -0,0 +1,34 @@ +class UserPolicy < ApplicationPolicy + + def index? + update? + end + + def update? + User.roles[user.role] >= PERMISSIONS[:auth_user_edit] + end + + def show? + true + end + + def destroy? + update? + end + + def create? + User.roles[user.role] >= PERMISSIONS[:auth_user_create] + end + + def profile? + true + end + + def all_api_keys? + update? + end + + def update_profile? + User.roles[user.role] >= PERMISSIONS[:auth_profile_edit] + end +end \ No newline at end of file diff --git a/app/views/api/v1/users/profile.json.jbuilder b/app/views/api/v1/users/profile.json.jbuilder index 0e451d6..d41f6a9 100644 --- a/app/views/api/v1/users/profile.json.jbuilder +++ b/app/views/api/v1/users/profile.json.jbuilder @@ -1,6 +1,6 @@ user ||= @userApiKey.user -json.extract! user, :email, :display_name, :created_at +json.extract! user, :email, :display_name, :created_at, :role, :role_permissions json.keys user.user_api_keys do |key| json.extract! key, :key diff --git a/config/routes.rb b/config/routes.rb index 29d9904..692e469 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,10 +16,12 @@ #post 'users', to: 'users#create' get 'users/profile', to: 'users#profile' put 'users/profile', to: 'users#update_profile' + resources :users, only: [:index, :show, :update] get 'all_api_keys', to: 'users#all_api_keys' get 'favourites', to: 'favourites#index' put 'favourites', to: 'favourites#update' get 'system/ping', to: 'system#ping' + get 'system/roles', to: 'system#roles' end end # The priority is based upon order of creation: first created -> highest priority. diff --git a/db/migrate/20150513190131_add_role_to_user.rb b/db/migrate/20150513190131_add_role_to_user.rb new file mode 100644 index 0000000..b24c7a6 --- /dev/null +++ b/db/migrate/20150513190131_add_role_to_user.rb @@ -0,0 +1,5 @@ +class AddRoleToUser < ActiveRecord::Migration + def change + add_column :users, :role, :integer, default: 0, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index fde3dc2..cfd872f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150406073147) do +ActiveRecord::Schema.define(version: 20150513190131) do create_table "activities", force: true do |t| t.integer "status" @@ -193,6 +193,7 @@ t.string "display_name" t.datetime "created_at" t.datetime "updated_at" + t.integer "role", default: 0, null: false end add_index "users", ["email"], name: "index_users_on_email", unique: true