summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/stylesheets/leap.scss21
-rw-r--r--app/controllers/identities_controller.rb28
-rw-r--r--app/helpers/link_helper.rb25
-rw-r--r--app/helpers/table_helper.rb13
-rw-r--r--app/models/identity.rb20
-rw-r--r--app/views/common/_action.html.haml2
-rw-r--r--app/views/common/_navigation_item.html.haml3
-rw-r--r--app/views/common/_table.html.haml18
-rw-r--r--app/views/identities/_identity.html.haml7
-rw-r--r--app/views/identities/index.html.haml4
-rw-r--r--app/views/layouts/_admin_navigation_item.html.haml3
-rw-r--r--app/views/layouts/_header.html.haml6
-rw-r--r--config/locales/users.en.yml7
-rw-r--r--config/routes.rb2
-rw-r--r--engines/support/app/views/tickets/index.html.haml16
-rw-r--r--test/factories.rb6
-rw-r--r--test/functional/identities_controller_test.rb42
-rw-r--r--test/integration/browser/admin_test.rb24
-rw-r--r--test/support/browser_integration_test.rb2
19 files changed, 227 insertions, 22 deletions
diff --git a/app/assets/stylesheets/leap.scss b/app/assets/stylesheets/leap.scss
index 77104e5..3b7075c 100644
--- a/app/assets/stylesheets/leap.scss
+++ b/app/assets/stylesheets/leap.scss
@@ -145,6 +145,27 @@ input, textarea {
}
//
+// IDENTITIES
+//
+
+// Color code for the identity labels
+.identity{
+ &.main_email .label {
+ @extend .label-info
+ }
+ &.alias .label {
+ @extend .label-success
+ }
+ &.forward .label {
+ @extend .label-warning
+ }
+ &.disabled .label {
+ @extend .label-default
+ }
+}
+
+
+//
// BORING DEFAULT MASTHEAD
//
diff --git a/app/controllers/identities_controller.rb b/app/controllers/identities_controller.rb
new file mode 100644
index 0000000..624e38a
--- /dev/null
+++ b/app/controllers/identities_controller.rb
@@ -0,0 +1,28 @@
+class IdentitiesController < ApplicationController
+
+ before_filter :require_login
+ before_filter :require_admin
+ before_filter :fetch_identity, only: :destroy
+ before_filter :protect_main_email, only: :destroy
+
+ def index
+ @identities = Identity.all
+ end
+
+ def destroy
+ @identity.destroy
+ redirect_to identities_path
+ end
+
+ protected
+ def fetch_identity
+ @identity = Identity.find(params[:id])
+ end
+
+ def protect_main_email
+ if @identity.status == :main_email
+ flash[:error] = "You cannot destroy the main email. Remove or Rename the user instead."
+ redirect_to identities_path
+ end
+ end
+end
diff --git a/app/helpers/link_helper.rb b/app/helpers/link_helper.rb
index 55e392b..ddb063e 100644
--- a/app/helpers/link_helper.rb
+++ b/app/helpers/link_helper.rb
@@ -1,5 +1,30 @@
module LinkHelper
+ Action = Struct.new(:target, :verb, :options) do
+ def to_partial_path; 'common/action'; end
+ def label; options[:label]; end
+ def class; verb; end
+ def url
+ case verb
+ when :show, :destroy then target
+ when :edit, :new then [verb, target]
+ end
+ end
+
+ def html_options
+ if verb == :destroy
+ {method: :delete}
+ end
+ end
+ end
+
+ def actions(target)
+ target.actions.map do |action|
+ Action.new target, action,
+ label: t(".#{action}", cascade: true)
+ end
+ end
+
#
# markup for bootstrap button
#
diff --git a/app/helpers/table_helper.rb b/app/helpers/table_helper.rb
new file mode 100644
index 0000000..16a7019
--- /dev/null
+++ b/app/helpers/table_helper.rb
@@ -0,0 +1,13 @@
+module TableHelper
+
+ # we do the translation here so the .key lookup is relative
+ # to the partial the helper was called from.
+ def table(content, columns)
+ render 'common/table',
+ content: content,
+ columns: columns,
+ headers: columns.map {|h| t(".#{h}", cascading: true) },
+ none: t('.none', cascading: true)
+ end
+
+end
diff --git a/app/models/identity.rb b/app/models/identity.rb
index e7b5785..e09d7f2 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -95,6 +95,18 @@ class Identity < CouchRest::Model::Base
}
end
+ def status
+ return :blocked if disabled?
+ case destination
+ when address
+ :main_email
+ when /@#{APP_CONFIG[:domain]}\Z/i,
+ :alias
+ else
+ :forward
+ end
+ end
+
def enabled?
self.user_id
end
@@ -103,6 +115,14 @@ class Identity < CouchRest::Model::Base
!enabled?
end
+ def actions
+ if enabled?
+ [] # [:show, :edit]
+ else
+ [:destroy]
+ end
+ end
+
def disable
self.destination = nil
self.user_id = nil
diff --git a/app/views/common/_action.html.haml b/app/views/common/_action.html.haml
new file mode 100644
index 0000000..71ffd96
--- /dev/null
+++ b/app/views/common/_action.html.haml
@@ -0,0 +1,2 @@
+%li.action{class: action.class}
+ =link_to action.label, action.url, action.html_options
diff --git a/app/views/common/_navigation_item.html.haml b/app/views/common/_navigation_item.html.haml
new file mode 100644
index 0000000..02c54c8
--- /dev/null
+++ b/app/views/common/_navigation_item.html.haml
@@ -0,0 +1,3 @@
+- item = navigation_item.to_s
+%li{:class => ("active" if controller?(item))}
+ = link_to t(".#{item}", cascade: true), polymorphic_url(item)
diff --git a/app/views/common/_table.html.haml b/app/views/common/_table.html.haml
new file mode 100644
index 0000000..3c6bf2e
--- /dev/null
+++ b/app/views/common/_table.html.haml
@@ -0,0 +1,18 @@
+-#
+-# A simple table with headers, content and a message if there is no
+-# content.
+-# You can use the table helper method to translate the headers and
+-# error message.
+-#
+%table.table.table-striped.table-bordered
+ %thead
+ %tr
+ - headers.each do |header|
+ %th= header
+ %tbody
+ - if content.any?
+ = render content.all
+ - else
+ %tr
+ %td{:colspan=>headers.count}= none
+
diff --git a/app/views/identities/_identity.html.haml b/app/views/identities/_identity.html.haml
new file mode 100644
index 0000000..7c4aeda
--- /dev/null
+++ b/app/views/identities/_identity.html.haml
@@ -0,0 +1,7 @@
+%tr[identity]{class: identity.status}
+ %td
+ %span.label= t(".#{identity.status}", cascading: true)
+ %td= identity.login
+ %td= identity.destination
+ %td
+ %ul.list-inline= render actions(identity)
diff --git a/app/views/identities/index.html.haml b/app/views/identities/index.html.haml
new file mode 100644
index 0000000..cabcccd
--- /dev/null
+++ b/app/views/identities/index.html.haml
@@ -0,0 +1,4 @@
+-@show_navigation = false
+
+= table @identities, %w(type username destination actions)
+-# = paginate @identities
diff --git a/app/views/layouts/_admin_navigation_item.html.haml b/app/views/layouts/_admin_navigation_item.html.haml
new file mode 100644
index 0000000..8dd8a39
--- /dev/null
+++ b/app/views/layouts/_admin_navigation_item.html.haml
@@ -0,0 +1,3 @@
+- item = admin_navigation_item.to_s
+%li{:class => ("active" if controller?(item))}
+ = link_to t(".#{item}", cascade: true), path_for(item)
diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml
index e827f60..ff14af9 100644
--- a/app/views/layouts/_header.html.haml
+++ b/app/views/layouts/_header.html.haml
@@ -1,10 +1,6 @@
- if admin?
%ul.nav.nav-tabs
- = # this navigation isn't quite right. also, we will want to active for an identity controller once it is added.
- %li{:class => ("active" if controller?('users', 'overviews') || params[:user_id])}
- = link_to t(".users"), users_path
- %li{:class => ("active" if controller?('tickets') && !params[:user_id])}
- = link_to t(".tickets", cascade: true), tickets_path
+ = render partial: 'common/navigation_item', collection: [:users, :identities, :tickets]
%li
= link_to t(:logout), logout_path, :method => :delete
- if @user && @show_navigation
diff --git a/config/locales/users.en.yml b/config/locales/users.en.yml
index f0fcb3d..bebf1cf 100644
--- a/config/locales/users.en.yml
+++ b/config/locales/users.en.yml
@@ -1,6 +1,9 @@
en:
- layouts:
- users: "Users"
+ common:
+ navigation_item:
+ users: "Users"
+ identities: "Usernames"
+ tickets: "Tickets"
user_control_panel: "user control panel"
account_settings: "Account Settings"
username: "Username"
diff --git a/config/routes.rb b/config/routes.rb
index 4ccfe62..468e14e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -48,6 +48,8 @@ LeapWeb::Application.routes.draw do
post 'deactivate', on: :member
post 'enable', on: :member
end
+
+ resources :identities, :only => [:index, :destroy]
end
get "/.well-known/host-meta" => 'webfinger#host_meta'
diff --git a/engines/support/app/views/tickets/index.html.haml b/engines/support/app/views/tickets/index.html.haml
index 526cd6d..d107ce2 100644
--- a/engines/support/app/views/tickets/index.html.haml
+++ b/engines/support/app/views/tickets/index.html.haml
@@ -1,19 +1,5 @@
- @show_navigation = params[:user_id].present?
= render 'tickets/tabs'
-
-%table.table.table-striped.table-bordered
- %thead
- %tr
- %th= t(".subject")
- %th= t(".created")
- %th= t(".updated")
- %th= t(".voices")
- %tbody
- - if @tickets.any?
- = render @tickets.all
- - else
- %tr
- %td{:colspan=>4}= t(".none")
-
+= table @tickets, %w(subject created updated voices)
= paginate @tickets
diff --git a/test/factories.rb b/test/factories.rb
index bebda5c..a96d48c 100644
--- a/test/factories.rb
+++ b/test/factories.rb
@@ -29,6 +29,12 @@ FactoryGirl.define do
end
+ # Identities can have a lot of different purposes - alias, forward, ...
+ # So far this is a blocked handle only.
+ factory :identity do
+ address {Faker::Internet.user_name + '@' + APP_CONFIG[:domain]}
+ end
+
factory :token do
user
end
diff --git a/test/functional/identities_controller_test.rb b/test/functional/identities_controller_test.rb
new file mode 100644
index 0000000..fcdeaa2
--- /dev/null
+++ b/test/functional/identities_controller_test.rb
@@ -0,0 +1,42 @@
+require 'test_helper'
+
+class IdentitiesControllerTest < ActionController::TestCase
+
+ test "admin can list active and blocked ids" do
+ login :is_admin? => true
+ get :index
+ assert_response :success
+ assert ids = assigns(:identities)
+ end
+
+ test "non-admin can't list usernames" do
+ login
+ get :index
+ assert_access_denied
+ end
+
+ test "requires login" do
+ get :index
+ assert_login_required
+ end
+
+ test "admin can unblock username" do
+ # an identity without user_id and destination is a blocked handle
+ identity = FactoryGirl.create :identity
+ login :is_admin? => true
+ delete :destroy, id: identity.id
+ assert_response :redirect
+ assert_nil Identity.find(identity.id)
+ end
+
+ test "admin cannot remove main identity" do
+ user = FactoryGirl.create :user
+ identity = FactoryGirl.create :identity,
+ Identity.attributes_from_user(user)
+ login :is_admin? => true
+ delete :destroy, id: identity.id
+ assert_response :redirect
+ assert_equal identity, Identity.find(identity.id)
+ end
+
+end
diff --git a/test/integration/browser/admin_test.rb b/test/integration/browser/admin_test.rb
new file mode 100644
index 0000000..2d3f988
--- /dev/null
+++ b/test/integration/browser/admin_test.rb
@@ -0,0 +1,24 @@
+require 'test_helper'
+
+class AdminTest < BrowserIntegrationTest
+
+ test "clear blocked handle" do
+ id = FactoryGirl.create :identity
+ submit_signup(id.login)
+ assert page.has_content?('has already been taken')
+ login
+ with_config admins: [@user.login] do
+ visit '/'
+ click_on "Usernames"
+ within "##{dom_id(id)}" do
+ assert page.has_content? id.login
+ click_on "Destroy"
+ end
+ assert page.has_no_content? id.login
+ click_on 'Log Out'
+ end
+ submit_signup(id.login)
+ assert page.has_content?("Welcome #{id.login}")
+ click_on 'Log Out'
+ end
+end
diff --git a/test/support/browser_integration_test.rb b/test/support/browser_integration_test.rb
index 4fec59f..c01b8a1 100644
--- a/test/support/browser_integration_test.rb
+++ b/test/support/browser_integration_test.rb
@@ -5,6 +5,8 @@
#
class BrowserIntegrationTest < ActionDispatch::IntegrationTest
+ # let's use dom_id inorder to identify sections
+ include ActionController::RecordIdentifier
CONFIG_RU = (Rails.root + 'config.ru').to_s
OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first