Merge pull request #1435 from internetee/improve-api-users

Improve API users
This commit is contained in:
Timo Võhmar 2019-12-16 17:18:17 +02:00 committed by GitHub
commit c168eac354
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 386 additions and 245 deletions

View file

@ -47,12 +47,6 @@ class @Autocomplete
selector: '.js-contact-typeahead'
hiddenSelector: '.js-contact-id'
@bindAdminRegistrarSearch: ->
Autocomplete.bindTypeahead
remote: '/admin/registrars/search'
selector: '.js-registrar-typeahead'
hiddenSelector: '.js-registrar-id'
@bindClientContactSearch: ->
Autocomplete.bindTypeahead
remote: '/client/contacts/search'

View file

@ -1,7 +1,6 @@
module Admin
class ApiUsersController < BaseController
load_and_authorize_resource
before_action :set_api_user, only: [:show, :edit, :update, :destroy]
def index
@q = ApiUser.includes(:registrar).search(params[:q])
@ -9,18 +8,17 @@ module Admin
end
def new
@registrar = Registrar.find_by(id: params[:registrar_id])
@api_user = ApiUser.new(registrar: @registrar)
@api_user = registrar.api_users.build
end
def create
@api_user = ApiUser.new(api_user_params)
@api_user = registrar.api_users.build(api_user_params)
if @api_user.save
flash[:notice] = I18n.t('record_created')
redirect_to [:admin, @api_user]
if @api_user.valid?
@api_user.save!
redirect_to admin_registrar_api_user_path(@api_user.registrar, @api_user),
notice: t('.created')
else
flash.now[:alert] = I18n.t('failed_to_create_record')
render 'new'
end
end
@ -32,39 +30,31 @@ module Admin
end
def update
if params[:api_user][:plain_text_password].blank?
params[:api_user].delete(:plain_text_password)
end
@api_user.attributes = api_user_params
if @api_user.update(api_user_params)
flash[:notice] = I18n.t('record_updated')
redirect_to [:admin, @api_user]
if @api_user.valid?
@api_user.save!
redirect_to admin_registrar_api_user_path(@api_user.registrar, @api_user),
notice: t('.updated')
else
flash.now[:alert] = I18n.t('failed_to_update_record')
render 'edit'
end
end
def destroy
if @api_user.destroy
flash[:notice] = I18n.t('record_deleted')
redirect_to admin_api_users_path
else
flash.now[:alert] = I18n.t('failed_to_delete_record')
render 'show'
end
@api_user.destroy!
redirect_to admin_registrar_path(@api_user.registrar), notice: t('.deleted')
end
private
def set_api_user
@api_user = ApiUser.find(params[:id])
end
def api_user_params
params.require(:api_user).permit(:username, :plain_text_password, :active,
:registrar_id, :registrar_typeahead,
:identity_code, { roles: [] })
end
def registrar
Registrar.find(params[:registrar_id])
end
end
end

View file

@ -34,7 +34,7 @@ module Admin
if @certificate.destroy
flash[:notice] = I18n.t('record_deleted')
redirect_to admin_api_user_path(@api_user)
redirect_to admin_registrar_api_user_path(@api_user.registrar, @api_user)
else
flash.now[:alert] = I18n.t('failed_to_delete_record')
render 'show'

View file

@ -28,7 +28,6 @@ class ApiUser < User
delegate :code, :name, to: :registrar, prefix: true
alias_attribute :login, :username
attr_accessor :registrar_typeahead
SUPER = 'super'
EPP = 'epp'
@ -53,10 +52,6 @@ class ApiUser < User
end
end
def registrar_typeahead
@registrar_typeahead || registrar || nil
end
def to_s
username
end

View file

@ -0,0 +1,5 @@
<tr>
<td><%= link_to api_user, admin_registrar_api_user_path(api_user.registrar, api_user) %></td>
<td><%= link_to api_user.registrar, admin_registrar_path(api_user.registrar) %></td>
<td><%= api_user.active %></td>
</tr>

View file

@ -1,57 +0,0 @@
= form_for([:admin, @api_user], multipart: true,
html: {class: 'form-horizontal', autocomplete: 'off'}) do |f|
= render 'shared/full_errors', object: @api_user
.row
.col-md-8
.form-group
.col-md-4.control-label
= f.label :username, nil, class: 'required'
.col-md-7
= f.text_field :username, required: true, autofocus: true, class: 'form-control'
.form-group
.col-md-4.control-label
= f.label :plain_text_password, nil, class: 'required'
.col-md-7
= f.text_field :plain_text_password, required: true, class: 'form-control'
.form-group
.col-md-4.control-label
= f.label :identity_code
.col-md-7
= f.text_field(:identity_code, class: 'form-control')
.form-group
.form-group.has-feedback.js-typeahead-container
.col-md-4.control-label
= f.label :registrar_typeahead, t(:registrar_name), class: 'required'
.col-md-7
= f.text_field(:registrar_typeahead,
class: 'form-control js-registrar-typeahead typeahead required',
placeholder: t(:registrar_name), autocomplete: 'off')
%span.glyphicon.glyphicon-ok.form-control-feedback.js-typeahead-ok.hidden
%span.glyphicon.glyphicon-remove.form-control-feedback.js-typeahead-remove
= f.hidden_field(:registrar_id, class: 'js-registrar-id')
.form-group
.col-md-4.control-label
= f.label :role, nil, class: 'required'
.col-md-7
= select_tag 'api_user[roles][]',
options_for_select(ApiUser::ROLES.map {|x| [x, x] }, @api_user.roles.try(:first)),
class: 'form-control selectize'
.checkbox
%label{for: 'api_user_active'}
= f.check_box(:active)
= t('.active')
%hr
.row
.col-md-8.text-right
= button_tag(t(:save), class: 'btn btn-primary')
:javascript
window.addEventListener('load', function() {
Autocomplete.bindAdminRegistrarSearch();
});

View file

@ -0,0 +1,58 @@
<%= form_for([:admin, @api_user.registrar, @api_user], html: { class: 'form-horizontal', autocomplete: 'off' }) do |f| %>
<%= render 'form_errors', target: @api_user %>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<div class="col-md-4 control-label">
<%= f.label :username, nil, class: 'required' %>
</div>
<div class="col-md-7">
<%= f.text_field :username, required: true, autofocus: true, class: 'form-control' %>
</div>
</div>
<div class="form-group">
<div class="col-md-4 control-label">
<%= f.label :plain_text_password, nil, class: 'required' %>
</div>
<div class="col-md-7">
<%= f.text_field :plain_text_password, required: true, class: 'form-control' %>
</div>
</div>
<div class="form-group">
<div class="col-md-4 control-label">
<%= f.label :identity_code %>
</div>
<div class="col-md-7">
<%= f.text_field(:identity_code, class: 'form-control') %>
</div>
</div>
<div class="form-group">
<div class="col-md-4 control-label">
<%= f.label :roles, nil, for: nil, class: 'required' %>
</div>
<div class="col-md-7">
<%= select_tag 'api_user[roles][]', options_for_select(ApiUser::ROLES.map { |x| [x, x] }, @api_user.roles.try(:first)), class: 'form-control selectize' %>
<div class="checkbox">
<label for="api_user_active">
<%= f.check_box(:active) %>
<%= ApiUser.human_attribute_name :active %>
</label>
</div>
</div>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-8 text-right">
<%= button_tag t(".#{f.object.new_record? ? 'create' : 'update'}_btn"), class: 'btn btn-success' %>
</div>
</div>
<% end %>

View file

@ -1,5 +0,0 @@
- content_for :actions do
= link_to(t(:back_to_api_user), [:admin, @api_user], class: 'btn btn-default')
= render 'shared/title', name: "#{t(:edit)}: #{@api_user.username}"
= render 'form'

View file

@ -0,0 +1,14 @@
<ol class="breadcrumb">
<li><%= link_to t('admin.registrars.index.header'), admin_registrars_path %></li>
<li><%= link_to @api_user.registrar, admin_registrar_path(@api_user.registrar) %></li>
<li><%= t 'admin.registrars.show.api_users.header' %></li>
<li><%= link_to @api_user.username, admin_registrar_api_user_path(@api_user.registrar,
@api_user) %></li>
<li><%= t '.header' %></li>
</ol>
<div class="page-header">
<h1><%= t '.header' %></h1>
</div>
<%= render 'form' %>

View file

@ -1,25 +0,0 @@
- content_for :actions do
= link_to(t('.new_btn'), new_admin_api_user_path, class: 'btn btn-primary')
= render 'shared/title', name: t('.title')
.row
.col-md-12
.table-responsive
%table.table.table-hover.table-bordered.table-condensed
%thead
%tr
%th{class: 'col-xs-2'}
= sort_link(@q, 'username')
%th{class: 'col-xs-2'}
= sort_link(@q, 'registrar_name', t(:registrar_name))
%th{class: 'col-xs-2'}
= sort_link(@q, 'active', t('.active'))
%tbody
- @api_users.each do |x|
%tr
%td= link_to(x, [:admin, x])
%td= link_to(x.registrar, [:admin, x.registrar])
%td= x.active
.row
.col-md-12
= paginate @api_users

View file

@ -0,0 +1,35 @@
<div class="page-header">
<h1><%= t '.header' %></h1>
</div>
<div class="row">
<div class="col-md-12">
<div class="table-responsive">
<table class="table table-hover table-bordered table-condensed">
<thead>
<tr>
<th class="col-xs-2">
<%= sort_link(@q, 'username') %>
</th>
<th class="col-xs-2">
<%= sort_link(@q, 'registrar_name', Registrar.model_name.human) %>
</th>
<th class="col-xs-2">
<%= sort_link(@q, 'active', ApiUser.human_attribute_name(:active)) %>
</th>
</tr>
</thead>
<tbody>
<%= render @api_users %>
</tbody>
</table>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<%= paginate @api_users %>
</div>
</div>

View file

@ -1,3 +0,0 @@
= render 'shared/title', name: t('.title')
= render 'form'

View file

@ -0,0 +1,11 @@
<ol class="breadcrumb">
<li><%= link_to t('admin.registrars.index.header'), admin_registrars_path %></li>
<li><%= link_to @api_user.registrar, admin_registrar_path(@api_user.registrar) %></li>
<li><%= t '.header' %></li>
</ol>
<div class="page-header">
<h1><%= t '.header' %></h1>
</div>
<%= render 'form' %>

View file

@ -1,61 +0,0 @@
- content_for :actions do
= link_to(t(:edit), edit_admin_api_user_path(@api_user), class: 'btn btn-default')
= link_to(t(:delete), admin_api_user_path(@api_user),
method: :delete, data: { confirm: t(:are_you_sure) }, class: 'btn btn-danger')
= render 'shared/title', name: @api_user.username
- if @api_user.errors.any?
- @api_user.errors.each do |attr, err|
= err
%br
- if @api_user.errors.any?
%hr
.row
.col-md-12
.panel.panel-default
.panel-heading
%h3.panel-title= t(:general)
.panel-body
%dl.dl-horizontal
%dt= t(:username)
%dd= @api_user.username
%dt= t(:password)
%dd= @api_user.plain_text_password
%dt= t(:registrar_name)
%dd= link_to(@api_user.registrar, admin_registrar_path(@api_user.registrar))
%dt= t(:role)
%dd= @api_user.roles.join(', ')
%dt= t('.active')
%dd= @api_user.active
.row
.col-md-12
.panel.panel-default
.panel-heading.clearfix
.pull-left
= t(:certificates)
.pull-right
= link_to(t(:upload_crt),
new_admin_api_user_certificate_path(@api_user, crt: true), class: 'btn btn-primary btn-xs')
= link_to(t(:upload_csr),
new_admin_api_user_certificate_path(@api_user), class: 'btn btn-primary btn-xs')
.table-responsive
%table.table.table-hover.table-bordered.table-condensed
%thead
%tr
%th{class: 'col-xs-10'}= t('.subject')
%th{class: 'col-xs-2'}= t(:status)
%tbody
- @api_user.certificates.each do |x|
- if x.csr
%tr
%td= link_to(x.parsed_csr.try(:subject), admin_api_user_certificate_path(@api_user, x))
%td= x.status
- elsif x.crt
%tr
%td= link_to(x.parsed_crt.try(:subject), admin_api_user_certificate_path(@api_user, x))
%td= x.status

View file

@ -0,0 +1,37 @@
<ol class="breadcrumb">
<li><%= link_to t('admin.registrars.index.header'), admin_registrars_path %></li>
<li><%= link_to @api_user.registrar, admin_registrar_path(@api_user.registrar) %></li>
<li><%= t 'admin.registrars.show.api_users.header' %></li>
<li><%= @api_user.username %></li>
</ol>
<div class="page-header">
<div class="row">
<div class="col-sm-8">
<h1><%= @api_user.username %></h1>
</div>
<div class="col-sm-4 text-right">
<%= link_to t('.edit_btn'), edit_admin_registrar_api_user_path(@api_user.registrar,
@api_user),
class: 'btn btn-primary' %>
<%= link_to t('.delete_btn'), admin_registrar_api_user_path(@api_user.registrar,
@api_user),
method: :delete,
data: { confirm: t('.delete_btn_confirm') },
class: 'btn btn-default' %>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<%= render 'admin/api_users/show/details' %>
</div>
</div>
<div class="row">
<div class="col-md-12">
<%= render 'admin/api_users/show/certificates' %>
</div>
</div>

View file

@ -0,0 +1,55 @@
<div class="panel panel-default">
<div class="panel-heading clearfix">
<div class="pull-left">
<%= t(:certificates) %>
</div>
<div class="pull-right">
<%= link_to(t(:upload_crt), new_admin_api_user_certificate_path(@api_user, crt: true), class: 'btn btn-primary btn-xs') %>
<%= link_to(t(:upload_csr), new_admin_api_user_certificate_path(@api_user), class: 'btn btn-primary btn-xs') %>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover table-bordered table-condensed">
<thead>
<tr>
<th class="col-xs-10">
<%= t('.subject') %>
</th>
<th class="col-xs-2">
<%= t(:status) %>
</th>
</tr>
</thead>
<tbody>
<% @api_user.certificates.each do |x| %>
<% if x.csr %>
<tr>
<td>
<%= link_to(x.parsed_csr.try(:subject),
admin_api_user_certificate_path(@api_user,
x)) %>
</td>
<td>
<%= x.status %>
</td>
</tr>
<% elsif x.crt %>
<tr>
<td>
<%= link_to(x.parsed_crt.try(:subject),
admin_api_user_certificate_path(@api_user,
x)) %>
</td>
<td>
<%= x.status %>
</td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
</div>

View file

@ -0,0 +1,26 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<%= t '.header' %>
</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt><%= ApiUser.human_attribute_name :username %></dt>
<dd><%= @api_user.username %></dd>
<dt><%= ApiUser.human_attribute_name :plain_text_password %></dt>
<dd><%= @api_user.plain_text_password %></dd>
<dt><%= Registrar.model_name.human %></dt>
<dd><%= link_to(@api_user.registrar, admin_registrar_path(@api_user.registrar)) %></dd>
<dt><%= ApiUser.human_attribute_name :roles %></dt>
<dd><%= @api_user.roles.join(', ') %></dd>
<dt><%= ApiUser.human_attribute_name :active %></dt>
<dd><%= @api_user.active %></dd>
</dl>
</div>
</div>

View file

@ -20,7 +20,7 @@
.panel-body
%dl.dl-horizontal
%dt= t(:api_user)
%dd= link_to(@certificate.api_user, [:admin, @api_user])
%dd= link_to(@certificate.api_user, [:admin, @api_user.registrar, @api_user])
%dt= t(:common_name)
%dd= @certificate.common_name

View file

@ -39,7 +39,7 @@
<div class="row">
<div class="col-md-12">
<%= render 'admin/registrars/show/users', registrar: @registrar %>
<%= render 'admin/registrars/show/api_users', registrar: @registrar %>
</div>
</div>

View file

@ -12,10 +12,10 @@
</thead>
<tbody>
<% registrar.api_users.each do |user| %>
<% registrar.api_users.each do |api_user| %>
<tr>
<td><%= link_to(user, [:admin, user]) %></td>
<td><%= user.active %></td>
<td><%= link_to api_user, admin_registrar_api_user_path(api_user.registrar, api_user) %></td>
<td><%= api_user.active %></td>
</tr>
<% end %>
</tbody>

View file

@ -2,16 +2,34 @@ en:
admin:
api_users:
index:
new_btn: New API user
title: API users
active: Active
show:
active: Active
subject: Subject
header: API users
new:
title: New API user
header: New API user
create:
created: API user has been successfully created
show:
edit_btn: Edit
delete_btn: Delete
delete_btn_confirm: Are you sure you want to delete this API user?
details:
header: Details
certificates:
subject: Subject
edit:
header: Edit API user
update:
updated: API user has been successfully updated
destroy:
deleted: API user has been successfully deleted
form:
active: Active
create_btn: Create API user
update_btn: Update API user

View file

@ -25,7 +25,7 @@ en:
preferences:
header: Preferences
users:
api_users:
header: API Users
new_btn: New API user
@ -67,4 +67,4 @@ en:
iban_hint: Used for e-invoices
preferences:
header: Preferences
header: Preferences

View file

@ -3,14 +3,4 @@ en:
attributes:
api_user:
plain_text_password: Password
errors:
models:
api_user:
attributes:
username:
blank: 'Username is missing'
taken: 'Username already exists'
plain_text_password:
blank: 'Password is missing'
registrar:
blank: 'Registrar is missing'
roles: Role

View file

@ -277,7 +277,6 @@ en:
certificate_signing_req: 'Certificate signing request'
csr: 'CSR'
crt: 'CRT'
back_to_api_user: 'Back to API user'
dnskey: 'DNS key'
dnskeys: 'DNS Keys'

View file

@ -259,7 +259,7 @@ Rails.application.routes.draw do
end
resources :registrars do
resources :api_users
resources :api_users, except: %i[index]
resources :white_ips
end
@ -270,7 +270,8 @@ Rails.application.routes.draw do
end
resources :admin_users
resources :api_users do
# /admin/api_users is mainly for manual testing
resources :api_users, only: :index do
resources :certificates do
member do
post 'sign'

View file

@ -1,25 +0,0 @@
require 'application_system_test_case'
class AdminAreaNewApiUserTest < ApplicationSystemTestCase
setup do
sign_in users(:admin)
end
def test_new_api_user_creation_with_required_params
visit admin_api_users_url
click_link_or_button 'New API user'
fill_in 'Username', with: 'newtest'
fill_in 'Password', with: 'testtest'
find('#api_user_registrar_id', visible: false).set(registrars(:bestnames).id)
assert_difference 'ApiUser.count' do
click_link_or_button 'Save'
end
assert_current_path admin_api_user_path(ApiUser.last)
assert_text 'Record created'
assert_text 'Username newtest'
assert_text 'Password testtest'
end
end

View file

@ -0,0 +1,14 @@
require 'application_system_test_case'
class AdminApiUsersSystemTest < ApplicationSystemTestCase
setup do
sign_in users(:admin)
end
def test_shows_api_user_list
visit admin_api_users_path
api_user = users(:api_bestnames)
assert_link api_user.username, href: admin_registrar_api_user_path(api_user.registrar, api_user)
end
end

View file

@ -0,0 +1,75 @@
require 'application_system_test_case'
class AdminRegistrarsApiUsersSystemTest < ApplicationSystemTestCase
setup do
sign_in users(:admin)
end
def test_creates_new_api_user_with_required_attributes
username = 'john'
registrar = registrars(:bestnames)
visit admin_registrar_path(registrar)
click_on 'New API user'
fill_in 'Username', with: username
fill_in 'Password', with: valid_password
click_on 'Create API user'
assert_text 'API user has been successfully created'
assert_text "Username #{username}"
new_api_user = ApiUser.last
assert_current_path admin_registrar_api_user_path(registrar, new_api_user)
end
def test_shows_api_user_details
api_user = users(:api_bestnames)
visit admin_registrar_path(api_user.registrar)
click_on api_user.username
assert_text "Username #{api_user.username}"
assert_text "Password #{api_user.plain_text_password}"
assert_link api_user.registrar.name, href: admin_registrar_path(api_user.registrar)
assert_text "Role #{api_user.roles.first}"
assert_text "Active #{api_user.active}"
end
def test_updates_api_user
api_user = users(:api_bestnames)
new_username = 'new username'
assert_not_equal new_username, api_user.username
visit admin_registrar_api_user_path(api_user.registrar, api_user)
click_link_or_button 'Edit'
fill_in 'Username', with: new_username
click_link_or_button 'Update API user'
assert_text 'API user has been successfully updated'
assert_text "Username #{new_username}"
assert_current_path admin_registrar_api_user_path(api_user.registrar, api_user)
end
def test_deletes_api_user
api_user = unassociated_api_user
visit admin_registrar_api_user_path(api_user.registrar, api_user)
click_on 'Delete'
assert_text 'API user has been successfully deleted'
assert_current_path admin_registrar_path(api_user.registrar)
end
private
def unassociated_api_user
new_api_user = users(:api_bestnames).dup
new_api_user.username = "unique-#{rand(100)}"
new_api_user.save!
new_api_user
end
def valid_password
'testtest'
end
end