diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/controllers/users_controller.rb | 27 | ||||
| -rw-r--r-- | app/helpers/users_helper.rb | 9 | ||||
| -rw-r--r-- | app/models/user.rb | 51 | ||||
| -rw-r--r-- | app/views/users/_user.html.erb | 13 | ||||
| -rw-r--r-- | app/views/users/edit.html.erb | 14 | ||||
| -rw-r--r-- | app/views/users/index.html.erb | 55 | ||||
| -rw-r--r-- | app/views/users/new.html.erb | 14 | ||||
| -rw-r--r-- | app/views/users/show.html.erb | 6 |
8 files changed, 135 insertions, 54 deletions
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index cb71db23..95dff220 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb | |||
| @@ -9,14 +9,20 @@ class UsersController < ApplicationController | |||
| 9 | 9 | ||
| 10 | layout 'admin' | 10 | layout 'admin' |
| 11 | 11 | ||
| 12 | ROLE_PRESETS = { | ||
| 13 | "editor" => [], | ||
| 14 | "redaktion" => ["redaktion"], | ||
| 15 | "admin" => ["admin", "redaktion"] | ||
| 16 | }.freeze | ||
| 17 | |||
| 18 | GROUP_ORDER = [:admin, :redaktion, :editor, :alumni].freeze | ||
| 19 | |||
| 12 | def index | 20 | def index |
| 13 | @users = User.order("login ASC").all.group_by do |user| | 21 | @users = User.order("login ASC").all.group_by(&:role_group) |
| 14 | user.admin? ? :admin : :user | ||
| 15 | end | ||
| 16 | end | 22 | end |
| 17 | 23 | ||
| 18 | def new | 24 | def new |
| 19 | @user = User.new(admin: params[:admin].present?) | 25 | @user = User.new(:roles => ROLE_PRESETS.fetch(params[:preset], [])) |
| 20 | end | 26 | end |
| 21 | 27 | ||
| 22 | def create | 28 | def create |
| @@ -35,8 +41,7 @@ class UsersController < ApplicationController | |||
| 35 | 41 | ||
| 36 | def update | 42 | def update |
| 37 | permitted = user_params | 43 | permitted = user_params |
| 38 | permitted.delete(:admin) unless current_user.is_admin? | 44 | |
| 39 | |||
| 40 | if @user.update(permitted) | 45 | if @user.update(permitted) |
| 41 | flash[:notice] = t("flash.users.updated", :login => @user.login) | 46 | flash[:notice] = t("flash.users.updated", :login => @user.login) |
| 42 | redirect_to user_path(@user) | 47 | redirect_to user_path(@user) |
| @@ -63,9 +68,13 @@ class UsersController < ApplicationController | |||
| 63 | private | 68 | private |
| 64 | 69 | ||
| 65 | def user_params | 70 | def user_params |
| 66 | allowed = [:login, :email, :password, :password_confirmation] | 71 | permitted = params.fetch(:user, {}) |
| 67 | allowed << :admin if current_user.admin? | 72 | .permit(:login, :email, :password, :password_confirmation, |
| 68 | params.fetch(:user, {}).permit(allowed) | 73 | :roles => []) |
| 74 | # Checkbox arrays post a leading blank from the hidden field. | ||
| 75 | permitted[:roles] = Array(permitted[:roles]).reject(&:blank?) if permitted.key?(:roles) | ||
| 76 | permitted.delete(:roles) unless current_user.is_admin? | ||
| 77 | permitted | ||
| 69 | end | 78 | end |
| 70 | 79 | ||
| 71 | def find_user | 80 | def find_user |
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb deleted file mode 100644 index ff031387..00000000 --- a/app/helpers/users_helper.rb +++ /dev/null | |||
| @@ -1,9 +0,0 @@ | |||
| 1 | module UsersHelper | ||
| 2 | def user_list_by_admin_status | ||
| 3 | if current_user && current_user.admin | ||
| 4 | render :partial => 'admin_user_item', :collection => @users | ||
| 5 | else | ||
| 6 | render :partial => 'user_item', :collection => @users | ||
| 7 | end | ||
| 8 | end | ||
| 9 | end | ||
diff --git a/app/models/user.rb b/app/models/user.rb index e1eff059..2e9da86c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb | |||
| @@ -9,6 +9,8 @@ class User < ApplicationRecord | |||
| 9 | include Authentication | 9 | include Authentication |
| 10 | include Authentication::ByPassword | 10 | include Authentication::ByPassword |
| 11 | 11 | ||
| 12 | ROLES = %w[redaktion admin alumni].freeze | ||
| 13 | |||
| 12 | # Validations | 14 | # Validations |
| 13 | validates_presence_of :login | 15 | validates_presence_of :login |
| 14 | validates_length_of :login, :within => 1..40 | 16 | validates_length_of :login, :within => 1..40 |
| @@ -22,6 +24,8 @@ class User < ApplicationRecord | |||
| 22 | validates_format_of :email, :with => Authentication.email_regex, | 24 | validates_format_of :email, :with => Authentication.email_regex, |
| 23 | :message => Authentication.bad_email_message | 25 | :message => Authentication.bad_email_message |
| 24 | 26 | ||
| 27 | validate :roles_are_known | ||
| 28 | |||
| 25 | # Authenticates a user by their login name and unencrypted password. Returns the user or nil. | 29 | # Authenticates a user by their login name and unencrypted password. Returns the user or nil. |
| 26 | def self.authenticate(login, password) | 30 | def self.authenticate(login, password) |
| 27 | return if login.blank? || password.blank? | 31 | return if login.blank? || password.blank? |
| @@ -60,7 +64,45 @@ class User < ApplicationRecord | |||
| 60 | end | 64 | end |
| 61 | 65 | ||
| 62 | def is_admin? | 66 | def is_admin? |
| 63 | !!admin | 67 | roles.include?("admin") |
| 68 | end | ||
| 69 | |||
| 70 | # Compatibility shims for the users form, which posts user[admin] as a | ||
| 71 | # checkbox. Goes away when that form learns about roles. | ||
| 72 | def admin | ||
| 73 | is_admin? | ||
| 74 | end | ||
| 75 | |||
| 76 | def admin? | ||
| 77 | is_admin? | ||
| 78 | end | ||
| 79 | |||
| 80 | def admin=(value) | ||
| 81 | if ActiveModel::Type::Boolean.new.cast(value) | ||
| 82 | self.roles = (roles | ["admin"]) | ||
| 83 | else | ||
| 84 | self.roles = (roles - ["admin"]) | ||
| 85 | end | ||
| 86 | end | ||
| 87 | |||
| 88 | def redaktion? | ||
| 89 | roles.include?("redaktion") | ||
| 90 | end | ||
| 91 | |||
| 92 | def alumni? | ||
| 93 | roles.include?("alumni") | ||
| 94 | end | ||
| 95 | |||
| 96 | def role_group | ||
| 97 | return :alumni if alumni? | ||
| 98 | return :admin if is_admin? | ||
| 99 | return :redaktion if redaktion? | ||
| 100 | :editor | ||
| 101 | end | ||
| 102 | |||
| 103 | # Human-readable role names, for the list and the forms. | ||
| 104 | def role_labels | ||
| 105 | roles.map { |r| I18n.t("users.roles.#{r}", :default => r) } | ||
| 64 | end | 106 | end |
| 65 | 107 | ||
| 66 | # otp_secret present == enrolled. otp_pending_secret holds the secret | 108 | # otp_secret present == enrolled. otp_pending_secret holds the secret |
| @@ -133,4 +175,11 @@ class User < ApplicationRecord | |||
| 133 | end | 175 | end |
| 134 | true | 176 | true |
| 135 | end | 177 | end |
| 178 | |||
| 179 | private | ||
| 180 | |||
| 181 | def roles_are_known | ||
| 182 | unknown = roles.to_a - ROLES | ||
| 183 | errors.add(:roles, :unknown, :list => unknown.join(", ")) if unknown.any? | ||
| 184 | end | ||
| 136 | end | 185 | end |
diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb index 9c6466a2..04884be8 100644 --- a/app/views/users/_user.html.erb +++ b/app/views/users/_user.html.erb | |||
| @@ -1,11 +1,16 @@ | |||
| 1 | <% users.each do |user| %> | 1 | <% users.each do |user| %> |
| 2 | <tr> | 2 | <tr> |
| 3 | <td class="user_login"><%= user.login %></td> | 3 | <td class="user_login"><%= user.login %></td> |
| 4 | <td><%= link_to "show", user_path(user) %></td> | 4 | <td class="user_roles"> |
| 5 | <% if current_user.admin? || current_user == user %> | 5 | <% if user.roles.any? %> |
| 6 | <td> | 6 | <%= user.role_labels.join(", ") %> |
| 7 | <%= link_to "edit", edit_user_path(user) %> | 7 | <% else %> |
| 8 | <span class="field_hint"><%= t(".no_roles") %></span> | ||
| 9 | <% end %> | ||
| 8 | </td> | 10 | </td> |
| 11 | <td><%= link_to t("admin.common.show"), user_path(user) %></td> | ||
| 12 | <% if current_user.admin? || current_user == user %> | ||
| 13 | <td><%= link_to t("admin.common.edit"), edit_user_path(user) %></td> | ||
| 9 | <td> | 14 | <td> |
| 10 | <%= button_to user_path(user), method: :delete, | 15 | <%= button_to user_path(user), method: :delete, |
| 11 | form: { data: { confirm: t(".confirm_destroy", :login => user.login) }, class: 'button_to destructive' } do %> | 16 | form: { data: { confirm: t(".confirm_destroy", :login => user.login) }, class: 'button_to destructive' } do %> |
diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 9df93815..ad2e0891 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb | |||
| @@ -21,8 +21,18 @@ | |||
| 21 | <div class="layout_row_content"><%= f.password_field :password_confirmation, :autocomplete => "new-password" %></div> | 21 | <div class="layout_row_content"><%= f.password_field :password_confirmation, :autocomplete => "new-password" %></div> |
| 22 | 22 | ||
| 23 | <% if current_user.admin? %> | 23 | <% if current_user.admin? %> |
| 24 | <div class="layout_row_label"><%= t("users.labels.admin") %></div> | 24 | <div class="layout_row_label"><%= t("users.labels.roles") %></div> |
| 25 | <div class="layout_row_content"><%= f.check_box :admin %></div> | 25 | <div class="layout_row_content"> |
| 26 | <% User::ROLES.each do |role| %> | ||
| 27 | <label class="role_choice"> | ||
| 28 | <%= check_box_tag "user[roles][]", role, @user.roles.include?(role), | ||
| 29 | :id => "user_roles_#{role}" %> | ||
| 30 | <%= t("users.roles.#{role}") %> | ||
| 31 | <span class="field_hint"><%= t("users.role_hints.#{role}") %></span> | ||
| 32 | </label> | ||
| 33 | <% end %> | ||
| 34 | <%= hidden_field_tag "user[roles][]", "" %> | ||
| 35 | </div> | ||
| 26 | <% end %> | 36 | <% end %> |
| 27 | 37 | ||
| 28 | <div class="layout_row_label"></div> | 38 | <div class="layout_row_label"></div> |
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 0a003cb1..854811a2 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb | |||
| @@ -1,27 +1,32 @@ | |||
| 1 | <h1><%= t(".admins") %></h1> | 1 | <h1><%= t(".title") %></h1> |
| 2 | <%= link_to new_user_path(admin: true), class: 'action_button' do %> | 2 | |
| 3 | <%= icon("plus", library: "tabler", "aria-hidden": true) %> <%= t(".create_admin") %> | 3 | <p class="button_row"> |
| 4 | <% end %> | 4 | <% UsersController::ROLE_PRESETS.each_key do |preset| %> |
| 5 | <table class="user_table"> | 5 | <%= link_to new_user_path(:preset => preset), class: 'action_button' do %> |
| 6 | <tr class="header"> | 6 | <%= icon("plus", library: "tabler", "aria-hidden": true) %> |
| 7 | <th><%= t("users.labels.login") %></th> | 7 | <%= t(".create_#{preset}") %> |
| 8 | <th></th> | 8 | <% end %> |
| 9 | <th></th> | 9 | <% end %> |
| 10 | <th></th> | 10 | </p> |
| 11 | </tr> | ||
| 12 | <%= render :partial => "user", :locals => {:users => @users[:admin] ||= []} %> | ||
| 13 | </table> | ||
| 14 | 11 | ||
| 15 | <h1><%= t(".users") %></h1> | 12 | <% UsersController::GROUP_ORDER.each do |group| %> |
| 16 | <%= link_to new_user_path, class: 'action_button' do %> | 13 | <% members = @users[group] || [] %> |
| 17 | <%= icon("plus", library: "tabler", "aria-hidden": true) %> <%= t(".create_user") %> | 14 | <h2 class="user_group_heading <%= "user_group_alumni" if group == :alumni %>"> |
| 15 | <%= t(".group_#{group}") %> | ||
| 16 | <span class="dashboard_widget_meta"><%= members.size %></span> | ||
| 17 | </h2> | ||
| 18 | <% if members.any? %> | ||
| 19 | <table class="user_table <%= "user_table_alumni" if group == :alumni %>"> | ||
| 20 | <tr class="header"> | ||
| 21 | <th><%= t("users.labels.login") %></th> | ||
| 22 | <th><%= t("users.labels.roles") %></th> | ||
| 23 | <th></th> | ||
| 24 | <th></th> | ||
| 25 | <th></th> | ||
| 26 | </tr> | ||
| 27 | <%= render :partial => "user", :locals => { :users => members } %> | ||
| 28 | </table> | ||
| 29 | <% else %> | ||
| 30 | <p class="field_hint"><%= t(".group_empty") %></p> | ||
| 31 | <% end %> | ||
| 18 | <% end %> | 32 | <% end %> |
| 19 | <table class="user_table"> | ||
| 20 | <tr class="header"> | ||
| 21 | <th><%= t("users.labels.login") %></th> | ||
| 22 | <th></th> | ||
| 23 | <th></th> | ||
| 24 | <th></th> | ||
| 25 | </tr> | ||
| 26 | <%= render :partial => "user", :locals => {:users => @users[:user] ||= []} %> | ||
| 27 | </table> | ||
diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index 776e6e96..8d99fd92 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb | |||
| @@ -21,8 +21,18 @@ | |||
| 21 | <div class="layout_row_label"><%= t("users.labels.confirm") %></div> | 21 | <div class="layout_row_label"><%= t("users.labels.confirm") %></div> |
| 22 | <div class="layout_row_content"><%= f.password_field :password_confirmation %></div> | 22 | <div class="layout_row_content"><%= f.password_field :password_confirmation %></div> |
| 23 | 23 | ||
| 24 | <div class="layout_row_label"><%= t("users.labels.admin") %></div> | 24 | <div class="layout_row_label"><%= t("users.labels.roles") %></div> |
| 25 | <div class="layout_row_content"><%= f.check_box :admin %></div> | 25 | <div class="layout_row_content"> |
| 26 | <% User::ROLES.each do |role| %> | ||
| 27 | <label class="role_choice"> | ||
| 28 | <%= check_box_tag "user[roles][]", role, @user.roles.include?(role), | ||
| 29 | :id => "user_roles_#{role}" %> | ||
| 30 | <%= t("users.roles.#{role}") %> | ||
| 31 | <span class="field_hint"><%= t("users.role_hints.#{role}") %></span> | ||
| 32 | </label> | ||
| 33 | <% end %> | ||
| 34 | <%= hidden_field_tag "user[roles][]", "" %> | ||
| 35 | </div> | ||
| 26 | 36 | ||
| 27 | <div class="layout_row_label"></div> | 37 | <div class="layout_row_label"></div> |
| 28 | <div class="layout_row_content"><%= f.submit t("admin.common.create") %></div> | 38 | <div class="layout_row_content"><%= f.submit t("admin.common.create") %></div> |
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 44976800..a320e53e 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb | |||
| @@ -19,7 +19,9 @@ | |||
| 19 | <div class="layout_row_label"><%= t("users.labels.email") %></div> | 19 | <div class="layout_row_label"><%= t("users.labels.email") %></div> |
| 20 | <div class="layout_row_content"><%= @user.email %></div> | 20 | <div class="layout_row_content"><%= @user.email %></div> |
| 21 | 21 | ||
| 22 | <div class="layout_row_label"><%= t("users.labels.admin") %></div> | 22 | <div class="layout_row_label"><%= t("users.labels.roles") %></div> |
| 23 | <div class="layout_row_content"><%= @user.admin ? t("admin.common.yes") : t("admin.common.no") %></div> | 23 | <div class="layout_row_content"> |
| 24 | <%= @user.roles.any? ? @user.role_labels.join(", ") : t("users.no_roles") %> | ||
| 25 | </div> | ||
| 24 | </div> | 26 | </div> |
| 25 | </div> | 27 | </div> |
