Browse Source

Added permission based/permission check setting api.

Martin Edenhofer 8 years ago
parent
commit
6bd5ae6471

+ 44 - 15
app/controllers/settings_controller.rb

@@ -5,29 +5,38 @@ class SettingsController < ApplicationController
 
   # GET /settings
   def index
-    model_index_render(Setting, params)
+    list = []
+    Setting.all.each { |setting|
+      next if setting.preferences[:permission] && !current_user.permissions?(setting.preferences[:permission])
+      list.push setting
+    }
+    render json: list, status: :ok
   end
 
   # GET /settings/1
   def show
+    check_access('read')
     model_show_render(Setting, params)
   end
 
   # POST /settings
   def create
-    model_create_render(Setting, params)
+    raise Exceptions::NotAuthorized, 'Not authorized (feature not possible)'
   end
 
   # PUT /settings/1
   def update
-    check_access
-    model_update_render(Setting, params)
+    check_access('write')
+    clean_params = keep_certain_attributes
+    model_update_render(Setting, clean_params)
   end
 
   # PUT /settings/image/:id
   def update_image
+    check_access('write')
+    clean_params = keep_certain_attributes
 
-    if !params[:logo]
+    if !clean_params[:logo]
       render json: {
         result: 'invalid',
         message: 'Need logo param',
@@ -36,7 +45,7 @@ class SettingsController < ApplicationController
     end
 
     # validate image
-    if params[:logo] !~ /^data:image/i
+    if clean_params[:logo] !~ /^data:image/i
       render json: {
         result: 'invalid',
         message: 'Invalid payload, need data:image in logo param',
@@ -45,7 +54,7 @@ class SettingsController < ApplicationController
     end
 
     # process image
-    file = StaticAssets.data_url_attributes(params[:logo])
+    file = StaticAssets.data_url_attributes(clean_params[:logo])
     if !file[:content] || !file[:mime_type]
       render json: {
         result: 'invalid',
@@ -58,7 +67,7 @@ class SettingsController < ApplicationController
     StaticAssets.store_raw(file[:content], file[:mime_type])
 
     # store resized image 1:1
-    setting = Setting.find_by(name: 'product_logo')
+    setting = Setting.lookup(name: 'product_logo')
     if params[:logo_resize] && params[:logo_resize] =~ /^data:image/i
 
       # data:image/png;base64
@@ -66,7 +75,7 @@ class SettingsController < ApplicationController
 
       # store image 1:1
       setting.state = StaticAssets.store(file[:content], file[:mime_type])
-      setting.save
+      setting.save!
     end
 
     render json: {
@@ -77,16 +86,36 @@ class SettingsController < ApplicationController
 
   # DELETE /settings/1
   def destroy
-    check_access
-    model_destory_render(Setting, params)
+    raise Exceptions::NotAuthorized, 'Not authorized (feature not possible)'
   end
 
   private
 
-  def check_access
-    return true if !Setting.get('system_online_service')
+  def keep_certain_attributes
     setting = Setting.find(params[:id])
-    return true if setting.preferences && !setting.preferences[:online_service_disable]
-    raise Exceptions::NotAuthorized
+    [:name, :area, :state_initial, :frontend, :options].each { |key|
+      params.delete(key)
+    }
+    [:online_service_disable, :permission, :render].each { |key|
+      params[:preferences].delete(key)
+    }
+    params[:preferences].merge!(setting.preferences)
+    params
+  end
+
+  def check_access(type)
+    setting = Setting.lookup(id: params[:id])
+
+    if setting.preferences[:permission] && !current_user.permissions?(setting.preferences[:permission])
+      raise Exceptions::NotAuthorized, "Not authorized (required #{setting.preferences[:permission].inspect})"
+    end
+
+    if type == 'write'
+      return true if !Setting.get('system_online_service')
+      if setting.preferences && setting.preferences[:online_service_disable]
+        raise Exceptions::NotAuthorized, 'Not authorized (service disabled)'
+      end
+    end
+    true
   end
 end

+ 12 - 2
app/models/user.rb

@@ -350,6 +350,8 @@ true or false for permission
 
   user.permissions?('permission') # access to all sub keys
 
+  user.permissions?('permission.*') # access if one sub key access exists
+
 returns
 
   true|false
@@ -368,8 +370,16 @@ returns
         cache = Cache.get(cache_key)
         return cache if cache
       end
-      permissions = Object.const_get('Permission').with_parents(local_key)
-      Object.const_get('Permission').joins(:roles).where('roles.id IN (?) AND permissions.name IN (?)', role_ids, permissions).each { |permission|
+      list = []
+      if local_key =~ /\.\*$/
+        local_key.sub!('.*', '.%')
+        permissions = Object.const_get('Permission').with_parents(local_key)
+        list = Object.const_get('Permission').joins(:roles).where('roles.id IN (?) AND (permissions.name IN (?) OR permissions.name LIKE ?)', role_ids, permissions, local_key)
+      else
+        permissions = Object.const_get('Permission').with_parents(local_key)
+        list = Object.const_get('Permission').joins(:roles).where('roles.id IN (?) AND permissions.name IN (?)', role_ids, permissions)
+      end
+      list.each { |permission|
         next if permission.preferences[:selectable] == false
         Cache.write(key, true, expires_in: 20.seconds)
         return true

+ 462 - 0
db/migrate/20160826000001_update_setting_permission.rb

@@ -0,0 +1,462 @@
+class UpdateSettingPermission < ActiveRecord::Migration
+  def up
+    # return if it's a new setup
+    return if !Setting.find_by(name: 'system_init_done')
+
+    updates = [
+      {
+        name: 'maintenance_mode',
+        preferences: {
+          permission: ['admin.maintenance']
+        },
+      },
+      {
+        name: 'maintenance_login',
+        preferences: {
+          permission: ['admin.maintenance']
+        },
+      },
+      {
+        name: 'maintenance_login_message',
+        preferences: {
+          permission: ['admin.maintenance']
+        },
+      },
+      {
+        name: 'product_name',
+        preferences: {
+          permission: ['admin.branding']
+        },
+      },
+      {
+        name: 'product_logo',
+        preferences: {
+          permission: ['admin.branding']
+        },
+      },
+      {
+        name: 'system_id',
+        preferences: {
+          permission: ['admin.system']
+        },
+      },
+      {
+        name: 'fqdn',
+        preferences: {
+          permission: ['admin.system']
+        },
+      },
+      {
+        name: 'http_type',
+        preferences: {
+          permission: ['admin.system']
+        },
+      },
+      {
+        name: 'storage',
+        preferences: {
+          permission: ['admin.system']
+        },
+      },
+      {
+        name: 'image_backend',
+        preferences: {
+          permission: ['admin.system']
+        },
+      },
+      {
+        name: 'geo_ip_backend',
+        preferences: {
+          permission: ['admin.system']
+        },
+      },
+      {
+        name: 'geo_location_backend',
+        preferences: {
+          permission: ['admin.system']
+        },
+      },
+      {
+        name: 'geo_calendar_backend',
+        preferences: {
+          permission: ['admin.system']
+        },
+      },
+      {
+        name: 'ui_send_client_stats',
+        preferences: {
+          permission: ['admin.system']
+        },
+      },
+      {
+        name: 'ui_client_storage',
+        preferences: {
+          permission: ['admin.system']
+        },
+      },
+      {
+        name: 'user_create_account',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'user_lost_password',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_ldap',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_twitter',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_twitter_credentials',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_facebook',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_facebook_credentials',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_google_oauth2',
+        preferences: {
+          permission: ['admin.maintenance']
+        },
+      },
+      {
+        name: 'maintenance_mode',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_google_oauth2_credentials',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_linkedin',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_linkedin_credentials',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_github',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_github_credentials',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_gitlab',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_gitlab_credentials',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_oauth2',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'auth_oauth2_credentials',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'password_min_size',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'password_min_2_lower_2_upper_characters',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'password_need_digit',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'password_max_login_failed',
+        preferences: {
+          permission: ['admin.security']
+        },
+      },
+      {
+        name: 'ticket_hook',
+        preferences: {
+          permission: ['admin.ticket']
+        },
+      },
+      {
+        name: 'ticket_hook_divider',
+        preferences: {
+          permission: ['admin.ticket']
+        },
+      },
+      {
+        name: 'ticket_hook_position',
+        preferences: {
+          permission: ['admin.ticket']
+        },
+      },
+      {
+        name: 'ticket_number',
+        preferences: {
+          permission: ['admin.ticket']
+        },
+      },
+      {
+        name: 'ticket_number_increment',
+        preferences: {
+          permission: ['admin.ticket']
+        },
+      },
+      {
+        name: 'ticket_number_date',
+        preferences: {
+          permission: ['admin.ticket']
+        },
+      },
+      {
+        name: 'customer_ticket_create',
+        preferences: {
+          permission: ['admin.channel_web']
+        },
+      },
+      {
+        name: 'customer_ticket_create_group_ids',
+        preferences: {
+          permission: ['admin.channel_web']
+        },
+      },
+      {
+        name: 'customer_ticket_view',
+        preferences: {
+          permission: ['admin.channel_web']
+        },
+      },
+      {
+        name: 'form_ticket_create',
+        preferences: {
+          permission: ['admin.channel_formular']
+        },
+      },
+      {
+        name: 'ticket_subject_size',
+        preferences: {
+          permission: ['admin.channel_email']
+        },
+      },
+      {
+        name: 'ticket_subject_re',
+        preferences: {
+          permission: ['admin.channel_email']
+        },
+      },
+      {
+        name: 'ticket_define_email_from',
+        preferences: {
+          permission: ['admin.channel_email']
+        },
+      },
+      {
+        name: 'ticket_define_email_from_seperator',
+        preferences: {
+          permission: ['admin.channel_email']
+        },
+      },
+      {
+        name: 'postmaster_max_size',
+        preferences: {
+          permission: ['admin.channel_email']
+        },
+      },
+      {
+        name: 'postmaster_follow_up_search_in',
+        preferences: {
+          permission: ['admin.channel_email']
+        },
+      },
+      {
+        name: 'notification_sender',
+        preferences: {
+          permission: ['admin.channel_email']
+        },
+      },
+      {
+        name: 'send_no_auto_response_reg_exp',
+        preferences: {
+          permission: ['admin.channel_email']
+        },
+      },
+      {
+        name: 'api_token_access',
+        preferences: {
+          permission: ['admin.api']
+        },
+      },
+      {
+        name: 'api_password_access',
+        preferences: {
+          permission: ['admin.api']
+        },
+      },
+      {
+        name: 'chat',
+        preferences: {
+          permission: ['admin.channel_chat']
+        },
+      },
+      {
+        name: 'chat_agent_idle_timeout',
+        preferences: {
+          permission: ['admin.channel_chat']
+        },
+      },
+      {
+        name: 'tag_new',
+        preferences: {
+          permission: ['admin.tag']
+        },
+      },
+      {
+        name: 'icinga_integration',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'icinga_sender',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'icinga_auto_close',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'icinga_auto_close_state_id',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'nagios_integration',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'nagios_sender',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'nagios_auto_close',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'nagios_auto_close_state_id',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'slack_integration',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'slack_config',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'sipgate_integration',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'sipgate_config',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'clearbit_integration',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+      {
+        name: 'clearbit_config',
+        preferences: {
+          permission: ['admin.integration']
+        },
+      },
+    ]
+
+    updates.each { |item|
+      setting = Setting.find_by(name: item[:name])
+      item[:preferences].each { |key, value|
+        setting.preferences[key] = value
+      }
+      setting.save!
+    }
+
+  end
+end

+ 229 - 49
db/seeds.rb

@@ -33,7 +33,9 @@ Setting.create_if_not_exists(
   description: 'Enable or disable the maintenance mode of Zammad. If enabled, all non-administrators get logged out and only administrators can start a new session.',
   options: {},
   state: false,
-  preferences: {},
+  preferences: {
+    permission: ['admin.maintenance'],
+  },
   frontend: true
 )
 Setting.create_if_not_exists(
@@ -43,7 +45,9 @@ Setting.create_if_not_exists(
   description: 'Put a message on the login page. To change it, click on the text area below and change it inline.',
   options: {},
   state: false,
-  preferences: {},
+  preferences: {
+    permission: ['admin.maintenance'],
+  },
   frontend: true
 )
 Setting.create_if_not_exists(
@@ -53,7 +57,9 @@ Setting.create_if_not_exists(
   description: 'Message for login page.',
   options: {},
   state: 'Something about to share. Click here to change.',
-  preferences: {},
+  preferences: {
+    permission: ['admin.maintenance'],
+  },
   frontend: true
 )
 Setting.create_if_not_exists(
@@ -91,7 +97,13 @@ Setting.create_if_not_exists(
       },
     ],
   },
-  preferences: { render: true, session_check: true, prio: 1, placeholder: true },
+  preferences: {
+    render: true,
+    session_check: true,
+    prio: 1,
+    placeholder: true,
+    permission: ['admin.branding'],
+  },
   state: 'Zammad Helpdesk',
   frontend: true
 )
@@ -113,6 +125,7 @@ Setting.create_if_not_exists(
   preferences: {
     prio: 3,
     controller: 'SettingsAreaLogo',
+    permission: ['admin.branding'],
   },
   state: 'logo.svg',
   frontend: true
@@ -133,7 +146,11 @@ Setting.create_if_not_exists(
     ],
   },
   state: '',
-  preferences: { prio: 2, placeholder: true },
+  preferences: {
+    prio: 2,
+    placeholder: true,
+    permission: ['admin.branding'],
+  },
   frontend: true
 )
 options = {}
@@ -162,6 +179,7 @@ Setting.create_if_not_exists(
     online_service_disable: true,
     placeholder: true,
     authentication: true,
+    permission: ['admin.system'],
   },
   frontend: true
 )
@@ -181,7 +199,11 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'zammad.example.com',
-  preferences: { online_service_disable: true, placeholder: true },
+  preferences: {
+    online_service_disable: true,
+    placeholder: true,
+    permission: ['admin.system'],
+  },
   frontend: true
 )
 Setting.create_if_not_exists(
@@ -223,7 +245,11 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'http',
-  preferences: { online_service_disable: true, placeholder: true },
+  preferences: {
+    online_service_disable: true,
+    placeholder: true,
+    permission: ['admin.system'],
+  },
   frontend: true
 )
 
@@ -247,7 +273,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'DB',
-  preferences: { online_service_disable: true },
+  preferences: {
+    online_service_disable: true,
+    permission: ['admin.system'],
+  },
   frontend: false
 )
 
@@ -271,7 +300,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'Service::Image::Zammad',
-  preferences: { prio: 1 },
+  preferences: {
+    prio: 1,
+    permission: ['admin.system'],
+  },
   frontend: false
 )
 
@@ -295,7 +327,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'Service::GeoIp::Zammad',
-  preferences: { prio: 2 },
+  preferences: {
+    prio: 2,
+    permission: ['admin.system'],
+  },
   frontend: false
 )
 
@@ -319,7 +354,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'Service::GeoLocation::Gmaps',
-  preferences: { prio: 3 },
+  preferences: {
+    prio: 3,
+    permission: ['admin.system'],
+  },
   frontend: false
 )
 
@@ -343,7 +381,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'Service::GeoCalendar::Zammad',
-  preferences: { prio: 2 },
+  preferences: {
+    prio: 2,
+    permission: ['admin.system'],
+  },
   frontend: false
 )
 
@@ -367,7 +408,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: true,
-  preferences: { prio: 1 },
+  preferences: {
+    prio: 1,
+    permission: ['admin.system'],
+  },
   frontend: true
 )
 Setting.create_if_not_exists(
@@ -390,7 +434,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: false,
-  preferences: { prio: 2 },
+  preferences: {
+    prio: 2,
+    permission: ['admin.system'],
+  },
   frontend: true
 )
 
@@ -414,6 +461,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: true,
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: true
 )
 Setting.create_if_not_exists(
@@ -436,6 +486,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: true,
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: true
 )
 Setting.create_if_not_exists(
@@ -445,7 +498,8 @@ Setting.create_if_not_exists(
   description: 'Enables user authentication via %s.',
   preferences: {
     title_i18n: ['LDAP'],
-    description_i18n: ['LDAP']
+    description_i18n: ['LDAP'],
+    permission: ['admin.security'],
   },
   state: {
     adapter: 'Auth::Ldap',
@@ -490,7 +544,8 @@ Setting.create_if_not_exists(
     controller: 'SettingsAreaSwitch',
     sub: ['auth_twitter_credentials'],
     title_i18n: ['Twitter'],
-    description_i18n: ['Twitter', 'Twitter Developer Site', 'https://dev.twitter.com/apps']
+    description_i18n: ['Twitter', 'Twitter Developer Site', 'https://dev.twitter.com/apps'],
+    permission: ['admin.security'],
   },
   state: false,
   frontend: true
@@ -517,6 +572,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: {},
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -542,7 +600,8 @@ Setting.create_if_not_exists(
     controller: 'SettingsAreaSwitch',
     sub: ['auth_facebook_credentials'],
     title_i18n: ['Facebook'],
-    description_i18n: ['Facebook', 'Facebook Developer Site', 'https://developers.facebook.com/apps/']
+    description_i18n: ['Facebook', 'Facebook Developer Site', 'https://developers.facebook.com/apps/'],
+    permission: ['admin.security'],
   },
   state: false,
   frontend: true
@@ -570,6 +629,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: {},
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 
@@ -596,7 +658,8 @@ Setting.create_if_not_exists(
     controller: 'SettingsAreaSwitch',
     sub: ['auth_google_oauth2_credentials'],
     title_i18n: ['Google'],
-    description_i18n: ['Google', 'Google API Console Site', 'https://console.developers.google.com/apis/credentials']
+    description_i18n: ['Google', 'Google API Console Site', 'https://console.developers.google.com/apis/credentials'],
+    permission: ['admin.security'],
   },
   state: false,
   frontend: true
@@ -623,6 +686,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: {},
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 
@@ -649,7 +715,8 @@ Setting.create_if_not_exists(
     controller: 'SettingsAreaSwitch',
     sub: ['auth_linkedin_credentials'],
     title_i18n: ['LinkedIn'],
-    description_i18n: ['LinkedIn', 'Linkedin Developer Site', 'https://www.linkedin.com/developer/apps']
+    description_i18n: ['LinkedIn', 'Linkedin Developer Site', 'https://www.linkedin.com/developer/apps'],
+    permission: ['admin.security'],
   },
   state: false,
   frontend: true
@@ -676,6 +743,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: {},
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 
@@ -702,7 +772,8 @@ Setting.create_if_not_exists(
     controller: 'SettingsAreaSwitch',
     sub: ['auth_github_credentials'],
     title_i18n: ['Github'],
-    description_i18n: ['Github', 'Github OAuth Applications', 'https://github.com/settings/applications']
+    description_i18n: ['Github', 'Github OAuth Applications', 'https://github.com/settings/applications'],
+    permission: ['admin.security'],
   },
   state: false,
   frontend: true
@@ -729,6 +800,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: {},
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 
@@ -755,7 +829,8 @@ Setting.create_if_not_exists(
     controller: 'SettingsAreaSwitch',
     sub: ['auth_gitlab_credentials'],
     title_i18n: ['Gitlab'],
-    description_i18n: ['Gitlab', 'Gitlab Applications', 'https://your-gitlab-host/admin/applications']
+    description_i18n: ['Gitlab', 'Gitlab Applications', 'https://your-gitlab-host/admin/applications'],
+    permission: ['admin.security'],
   },
   state: false,
   frontend: true
@@ -789,6 +864,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: {},
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 
@@ -815,6 +893,7 @@ Setting.create_if_not_exists(
     controller: 'SettingsAreaSwitch',
     sub: ['auth_oauth2_credentials'],
     title_i18n: ['Generic OAuth2'],
+    permission: ['admin.security'],
   },
   state: false,
   frontend: true
@@ -869,6 +948,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: {},
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 
@@ -907,6 +989,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: 6,
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -929,6 +1014,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: 0,
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -951,6 +1039,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: 1,
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -987,6 +1078,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: 10,
+  preferences: {
+    permission: ['admin.security'],
+  },
   frontend: false
 )
 
@@ -1009,6 +1103,7 @@ Setting.create_if_not_exists(
     render: true,
     placeholder: true,
     authentication: true,
+    permission: ['admin.ticket'],
   },
   state: 'Ticket#',
   frontend: true
@@ -1029,6 +1124,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: '',
+  preferences: {
+    permission: ['admin.ticket'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -1055,6 +1153,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'right',
+  preferences: {
+    permission: ['admin.ticket'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -1081,6 +1182,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'Ticket::Number::Increment',
+  preferences: {
+    permission: ['admin.ticket'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -1134,6 +1238,9 @@ Setting.create_if_not_exists(
     checksum: false,
     min_size: 5,
   },
+  preferences: {
+    permission: ['admin.ticket'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -1158,6 +1265,9 @@ Setting.create_if_not_exists(
   state: {
     checksum: false,
   },
+  preferences: {
+    permission: ['admin.ticket'],
+  },
   frontend: false
 )
 
@@ -1183,6 +1293,7 @@ Setting.create_if_not_exists(
   state: true,
   preferences: {
     authentication: true,
+    permission: ['admin.channel_web'],
   },
   frontend: true
 )
@@ -1208,6 +1319,7 @@ Setting.create_if_not_exists(
   state: '',
   preferences: {
     authentication: true,
+    permission: ['admin.channel_web'],
   },
   frontend: true
 )
@@ -1234,6 +1346,7 @@ Setting.create_if_not_exists(
   state: true,
   preferences: {
     authentication: true,
+    permission: ['admin.channel_web'],
   },
   frontend: true
 )
@@ -1258,6 +1371,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: false,
+  preferences: {
+    permission: ['admin.channel_formular'],
+  },
   frontend: false,
 )
 
@@ -1277,6 +1393,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: '110',
+  preferences: {
+    permission: ['admin.channel_email'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -1295,6 +1414,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'RE',
+  preferences: {
+    permission: ['admin.channel_email'],
+  },
   frontend: false
 )
 
@@ -1318,6 +1440,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'AgentNameSystemAddressName',
+  preferences: {
+    permission: ['admin.channel_email'],
+  },
   frontend: false
 )
 
@@ -1337,6 +1462,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'via',
+  preferences: {
+    permission: ['admin.channel_email'],
+  },
   frontend: false
 )
 
@@ -1383,7 +1511,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: 10,
-  preferences: { online_service_disable: true },
+  preferences: {
+    online_service_disable: true,
+    permission: ['admin.channel_email'],
+  },
   frontend: false
 )
 
@@ -1408,6 +1539,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: [],
+  preferences: {
+    permission: ['admin.channel_email'],
+  },
   frontend: false
 )
 
@@ -1427,7 +1561,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'Notification Master <noreply@#{config.fqdn}>',
-  preferences: { online_service_disable: true },
+  preferences: {
+    online_service_disable: true,
+    permission: ['admin.channel_email'],
+  },
   frontend: false
 )
 
@@ -1447,7 +1584,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: '(mailer-daemon|postmaster|abuse|root|noreply|noreply.+?|no-reply|no-reply.+?)@.+?\..+?',
-  preferences: { online_service_disable: true },
+  preferences: {
+    online_service_disable: true,
+    permission: ['admin.channel_email'],
+  },
   frontend: false
 )
 
@@ -1471,6 +1611,9 @@ Setting.create_or_update(
     ],
   },
   state: true,
+  preferences: {
+    permission: ['admin.api'],
+  },
   frontend: false
 )
 Setting.create_or_update(
@@ -1493,6 +1636,9 @@ Setting.create_or_update(
     ],
   },
   state: true,
+  preferences: {
+    permission: ['admin.api'],
+  },
   frontend: false
 )
 
@@ -1516,7 +1662,8 @@ Setting.create_if_not_exists(
     ],
   },
   preferences: {
-    trigger: ['menu:render', 'chat:rerender']
+    trigger: ['menu:render', 'chat:rerender'],
+    permission: ['admin.channel_chat'],
   },
   state: false,
   frontend: true
@@ -1538,6 +1685,9 @@ Setting.create_if_not_exists(
     ],
   },
   state: '120',
+  preferences: {
+    permission: ['admin.channel_chat'],
+  },
   frontend: true
 )
 
@@ -1825,6 +1975,7 @@ Setting.create_if_not_exists(
   },
   preferences: {
     authentication: true,
+    permission: ['admin.tag'],
   },
   state: true,
   frontend: true
@@ -1976,7 +2127,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: false,
-  preferences: { prio: 1 },
+  preferences: {
+    prio: 1,
+    permission: ['admin.integration'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -1996,8 +2150,11 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'icinga@monitoring.example.com',
+  preferences: {
+    prio: 2,
+    permission: ['admin.integration'],
+  },
   frontend: false,
-  preferences: { prio: 2 },
 )
 Setting.create_if_not_exists(
   title: 'Auto close',
@@ -2019,7 +2176,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: true,
-  preferences: { prio: 3 },
+  preferences: {
+    prio: 3,
+    permission: ['admin.integration'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -2039,7 +2199,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: 4,
-  preferences: { prio: 4 },
+  preferences: {
+    prio: 4,
+    permission: ['admin.integration'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -2062,7 +2225,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: false,
-  preferences: { prio: 1 },
+  preferences: {
+    prio: 1,
+    permission: ['admin.integration'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -2082,8 +2248,11 @@ Setting.create_if_not_exists(
     ],
   },
   state: 'nagios@monitoring.example.com',
+  preferences: {
+    prio: 2,
+    permission: ['admin.integration'],
+  },
   frontend: false,
-  preferences: { prio: 2 },
 )
 Setting.create_if_not_exists(
   title: 'Auto close',
@@ -2105,7 +2274,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: true,
-  preferences: { prio: 3 },
+  preferences: {
+    prio: 3,
+    permission: ['admin.integration'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -2125,7 +2297,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: 4,
-  preferences: { prio: 4 },
+  preferences: {
+    prio: 4,
+    permission: ['admin.integration'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -2184,7 +2359,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: false,
-  preferences: { prio: 1 },
+  preferences: {
+    prio: 1,
+    permission: ['admin.integration'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -2196,8 +2374,11 @@ Setting.create_if_not_exists(
   state: {
     items: []
   },
+  preferences: {
+    prio: 2,
+    permission: ['admin.integration'],
+  },
   frontend: false,
-  preferences: { prio: 2 },
 )
 Setting.create_if_not_exists(
   title: 'sipgate.io integration',
@@ -2223,6 +2404,7 @@ Setting.create_if_not_exists(
     prio: 1,
     trigger: ['menu:render', 'cti:reload'],
     authentication: true,
+    permission: ['admin.integration'],
   },
   frontend: true
 )
@@ -2233,8 +2415,11 @@ Setting.create_if_not_exists(
   description: 'Define the sipgate.io config.',
   options: {},
   state: {},
+  preferences: {
+    prio: 2,
+    permission: ['admin.integration'],
+  },
   frontend: false,
-  preferences: { prio: 2 },
 )
 Setting.create_if_not_exists(
   title: 'Clearbit integration',
@@ -2256,7 +2441,10 @@ Setting.create_if_not_exists(
     ],
   },
   state: false,
-  preferences: { prio: 1 },
+  preferences: {
+    prio: 1,
+    permission: ['admin.integration'],
+  },
   frontend: false
 )
 Setting.create_if_not_exists(
@@ -2267,7 +2455,10 @@ Setting.create_if_not_exists(
   options: {},
   state: {},
   frontend: false,
-  preferences: { prio: 2 },
+  preferences: {
+    prio: 2,
+    permission: ['admin.integration'],
+  },
 )
 Setting.create_if_not_exists(
   title: 'Define transaction backend.',
@@ -2395,17 +2586,6 @@ Role.create_if_not_exists(
   updated_by_id: 1,
   created_by_id: 1
 )
-Role.create_if_not_exists(
-  id: 4,
-  name: 'Report',
-  note: 'Access the report area.',
-  preferences: {
-    not: ['Customer'],
-  },
-  default_at_signup: false,
-  created_by_id: 1,
-  updated_by_id: 1,
-)
 
 Permission.create_if_not_exists(
   name: 'admin',

+ 202 - 1
test/controllers/settings_controller_test.rb

@@ -12,7 +12,7 @@ class SettingsControllerTest < ActionDispatch::IntegrationTest
     groups = Group.all
 
     UserInfo.current_user_id = 1
-    @admin = User.create_or_update(
+    @admin_full = User.create_or_update(
       login: 'setting-admin',
       firstname: 'Setting',
       lastname: 'Admin',
@@ -23,6 +23,28 @@ class SettingsControllerTest < ActionDispatch::IntegrationTest
       groups: groups,
     )
 
+    role_api = Role.create_or_update(
+      name: 'AdminApi',
+      note: 'To configure your api.',
+      preferences: {
+        not: ['Customer'],
+      },
+      default_at_signup: false,
+      updated_by_id: 1,
+      created_by_id: 1
+    )
+    role_api.permission_grand('admin.api')
+    @admin_api = User.create_or_update(
+      login: 'setting-admin-api',
+      firstname: 'Setting',
+      lastname: 'Admin Api',
+      email: 'setting-admin-api@example.com',
+      password: 'adminpw',
+      active: true,
+      roles: [role_api],
+      groups: groups,
+    )
+
     # create agent
     roles = Role.where(name: 'Agent')
     @agent = User.create_or_update(
@@ -58,6 +80,13 @@ class SettingsControllerTest < ActionDispatch::IntegrationTest
     result = JSON.parse(@response.body)
     assert_equal(Hash, result.class)
     assert_not(result['settings'])
+
+    # show
+    setting = Setting.find_by(name: 'product_name')
+    get "/api/v1/settings/#{setting.id}", {}
+    assert_response(401)
+    result = JSON.parse(@response.body)
+    assert_equal('Not authorized', result['error'])
   end
 
   test 'settings index with admin' do
@@ -70,6 +99,157 @@ class SettingsControllerTest < ActionDispatch::IntegrationTest
     result = JSON.parse(@response.body)
     assert_equal(Array, result.class)
     assert(result)
+    hit_api = false
+    hit_product_name = false
+    result.each { |setting|
+      if setting['name'] == 'api_token_access'
+        hit_api = true
+      end
+      if setting['name'] == 'product_name'
+        hit_product_name = true
+      end
+    }
+    assert_equal(true, hit_api)
+    assert_equal(true, hit_product_name)
+
+    # show
+    setting = Setting.find_by(name: 'product_name')
+    get "/api/v1/settings/#{setting.id}", {}, @headers.merge('Authorization' => credentials)
+    assert_response(200)
+    result = JSON.parse(@response.body)
+    assert_equal(Hash, result.class)
+    assert_equal('product_name', result['name'])
+
+    setting = Setting.find_by(name: 'api_token_access')
+    get "/api/v1/settings/#{setting.id}", {}, @headers.merge('Authorization' => credentials)
+    assert_response(200)
+    result = JSON.parse(@response.body)
+    assert_equal(Hash, result.class)
+    assert_equal('api_token_access', result['name'])
+
+    # update
+    setting = Setting.find_by(name: 'product_name')
+    params = {
+      id: setting.id,
+      name: 'some_new_name',
+      preferences: {
+        permission: ['admin.branding', 'admin.some_new_permission'],
+        some_new_key: true,
+      }
+    }
+    put "/api/v1/settings/#{setting.id}", params.to_json, @headers.merge('Authorization' => credentials)
+    assert_response(200)
+    result = JSON.parse(@response.body)
+    assert_equal(Hash, result.class)
+    assert_equal('product_name', result['name'])
+    assert_equal(1, result['preferences']['permission'].length)
+    assert_equal('admin.branding', result['preferences']['permission'][0])
+    assert_equal(true, result['preferences']['some_new_key'])
+
+    # update
+    setting = Setting.find_by(name: 'api_token_access')
+    params = {
+      id: setting.id,
+      name: 'some_new_name',
+      preferences: {
+        permission: ['admin.branding', 'admin.some_new_permission'],
+        some_new_key: true,
+      }
+    }
+    put "/api/v1/settings/#{setting.id}", params.to_json, @headers.merge('Authorization' => credentials)
+    assert_response(200)
+    result = JSON.parse(@response.body)
+    assert_equal(Hash, result.class)
+    assert_equal('api_token_access', result['name'])
+    assert_equal(1, result['preferences']['permission'].length)
+    assert_equal('admin.api', result['preferences']['permission'][0])
+    assert_equal(true, result['preferences']['some_new_key'])
+
+    # delete
+    setting = Setting.find_by(name: 'product_name')
+    delete "/api/v1/settings/#{setting.id}", {}.to_json, @headers.merge('Authorization' => credentials)
+    assert_response(401)
+    result = JSON.parse(@response.body)
+    assert_equal('Not authorized (feature not possible)', result['error'])
+  end
+
+  test 'settings index with admin-api' do
+
+    credentials = ActionController::HttpAuthentication::Basic.encode_credentials('setting-admin-api@example.com', 'adminpw')
+
+    # index
+    get '/api/v1/settings', {}, @headers.merge('Authorization' => credentials)
+    assert_response(200)
+    result = JSON.parse(@response.body)
+    assert_equal(Array, result.class)
+    assert(result)
+    hit_api = false
+    hit_product_name = false
+    result.each { |setting|
+      if setting['name'] == 'api_token_access'
+        hit_api = true
+      end
+      if setting['name'] == 'product_name'
+        hit_product_name = true
+      end
+    }
+    assert_equal(true, hit_api)
+    assert_equal(false, hit_product_name)
+
+    # show
+    setting = Setting.find_by(name: 'product_name')
+    get "/api/v1/settings/#{setting.id}", {}, @headers.merge('Authorization' => credentials)
+    assert_response(401)
+    result = JSON.parse(@response.body)
+    assert_equal('Not authorized (required ["admin.branding"])', result['error'])
+
+    setting = Setting.find_by(name: 'api_token_access')
+    get "/api/v1/settings/#{setting.id}", {}, @headers.merge('Authorization' => credentials)
+    assert_response(200)
+    result = JSON.parse(@response.body)
+    assert_equal(Hash, result.class)
+    assert_equal('api_token_access', result['name'])
+
+    # update
+    setting = Setting.find_by(name: 'product_name')
+    params = {
+      id: setting.id,
+      name: 'some_new_name',
+      preferences: {
+        permission: ['admin.branding', 'admin.some_new_permission'],
+        some_new_key: true,
+      }
+    }
+    put "/api/v1/settings/#{setting.id}", params.to_json, @headers.merge('Authorization' => credentials)
+    assert_response(401)
+    result = JSON.parse(@response.body)
+    assert_equal('Not authorized (required ["admin.branding"])', result['error'])
+
+    # update
+    setting = Setting.find_by(name: 'api_token_access')
+    params = {
+      id: setting.id,
+      name: 'some_new_name',
+      preferences: {
+        permission: ['admin.branding', 'admin.some_new_permission'],
+        some_new_key: true,
+      }
+    }
+    put "/api/v1/settings/#{setting.id}", params.to_json, @headers.merge('Authorization' => credentials)
+    assert_response(200)
+    result = JSON.parse(@response.body)
+    assert_equal(Hash, result.class)
+    assert_equal('api_token_access', result['name'])
+    assert_equal(1, result['preferences']['permission'].length)
+    assert_equal('admin.api', result['preferences']['permission'][0])
+    assert_equal(true, result['preferences']['some_new_key'])
+
+    # delete
+    setting = Setting.find_by(name: 'product_name')
+    delete "/api/v1/settings/#{setting.id}", {}.to_json, @headers.merge('Authorization' => credentials)
+    assert_response(401)
+    result = JSON.parse(@response.body)
+    assert_equal('Not authorized (feature not possible)', result['error'])
   end
 
   test 'settings index with agent' do
@@ -83,6 +263,13 @@ class SettingsControllerTest < ActionDispatch::IntegrationTest
     assert_equal(Hash, result.class)
     assert_not(result['settings'])
     assert_equal('Not authorized (user)!', result['error'])
+
+    # show
+    setting = Setting.find_by(name: 'product_name')
+    get "/api/v1/settings/#{setting.id}", {}, @headers.merge('Authorization' => credentials)
+    assert_response(401)
+    result = JSON.parse(@response.body)
+    assert_equal('Not authorized (user)', result['error'])
   end
 
   test 'settings index with customer' do
@@ -96,6 +283,20 @@ class SettingsControllerTest < ActionDispatch::IntegrationTest
     assert_equal(Hash, result.class)
     assert_not(result['settings'])
     assert_equal('Not authorized (user)!', result['error'])
+
+    # show
+    setting = Setting.find_by(name: 'product_name')
+    get "/api/v1/settings/#{setting.id}", {}, @headers.merge('Authorization' => credentials)
+    assert_response(401)
+    result = JSON.parse(@response.body)
+    assert_equal('Not authorized (user)', result['error'])
+
+    # delete
+    setting = Setting.find_by(name: 'product_name')
+    delete "/api/v1/settings/#{setting.id}", {}.to_json, @headers.merge('Authorization' => credentials)
+    assert_response(401)
+    result = JSON.parse(@response.body)
+    assert_equal('Not authorized (feature not possible)', result['error'])
   end
 
 end

+ 1 - 1
test/integration/zendesk_import_test.rb

@@ -46,7 +46,7 @@ class ZendeskImportTest < ActiveSupport::TestCase
   test 'check counts' do
     assert_equal(143, User.count, 'users')
     assert_equal(3, Group.count, 'groups')
-    assert_equal(4, Role.count, 'roles')
+    assert_equal(3, Role.count, 'roles')
     assert_equal(2, Organization.count, 'organizations')
     assert_equal(143, Ticket.count, 'tickets')
     assert_equal(151, Ticket::Article.count, 'ticket articles')

+ 37 - 0
test/unit/permission_test.rb

@@ -10,4 +10,41 @@ class PermissionTest < ActiveSupport::TestCase
     assert_equal(2, permissions.count)
   end
 
+  test 'user permission' do
+
+    Permission.create_if_not_exists(
+      name: 'admin.permission1',
+      note: 'Admin Interface',
+      preferences: {},
+    )
+    role_permission1 = Role.create_or_update(
+      name: 'AdminPermission1',
+      note: 'To configure your permission1.',
+      preferences: {
+        not: ['Customer'],
+      },
+      default_at_signup: false,
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    role_permission1.permission_grand('admin.permission1')
+    user_with_permission1 = User.create_or_update(
+      login: 'setting-permission1',
+      firstname: 'Setting',
+      lastname: 'Admin Permission1',
+      email: 'setting-admin-permission1@example.com',
+      password: 'some_pw',
+      active: true,
+      roles: [role_permission1],
+      updated_by_id: 1,
+      created_by_id: 1,
+    )
+    assert_equal(true, user_with_permission1.permissions?('admin.permission1'))
+    assert_equal(true, user_with_permission1.permissions?('admin.*'))
+    assert_equal(false, user_with_permission1.permissions?('admi.*'))
+    assert_equal(false, user_with_permission1.permissions?('admin.permission2'))
+    assert_equal(false, user_with_permission1.permissions?('admin'))
+
+  end
+
 end