summaryrefslogtreecommitdiff
path: root/users/app
diff options
context:
space:
mode:
authorjessib <jessib@riseup.net>2013-12-17 12:02:07 -0800
committerjessib <jessib@riseup.net>2013-12-17 12:02:07 -0800
commit98a247acb4d1be484c2982d48ec038eed3de3d55 (patch)
treef176092fa43145b9d8ce00a180fab70cf62da456 /users/app
parent634db9875cc8f6f6b9a4a83dfc6b8d53728eb2b5 (diff)
parent83cd3d95b78b6df9ac33a8ee169e570f4d3e2eeb (diff)
Merge branch 'develop' into feature/billing-no-authenticated-payments
Conflicts: billing/config/locales/en.yml
Diffstat (limited to 'users/app')
m---------users/app/assets/javascripts/srp0
-rw-r--r--users/app/assets/javascripts/users.js51
-rw-r--r--users/app/controllers/keys_controller.rb18
-rw-r--r--users/app/controllers/overviews_controller.rb9
-rw-r--r--users/app/controllers/sessions_controller.rb5
-rw-r--r--users/app/controllers/users_controller.rb20
-rw-r--r--users/app/controllers/v1/users_controller.rb8
-rw-r--r--users/app/models/account.rb25
-rw-r--r--users/app/models/identity.rb42
-rw-r--r--users/app/models/local_email.rb33
-rw-r--r--users/app/models/pgp_key.rb48
-rw-r--r--users/app/models/service_level.rb19
-rw-r--r--users/app/models/token.rb36
-rw-r--r--users/app/models/unauthenticated_user.rb2
-rw-r--r--users/app/models/user.rb24
-rw-r--r--users/app/views/overviews/show.html.haml22
-rw-r--r--users/app/views/users/_change_password.html.haml21
-rw-r--r--users/app/views/users/_change_pgp_key.html.haml13
-rw-r--r--users/app/views/users/_change_service_level.html.haml18
-rw-r--r--users/app/views/users/_destroy_account.html.haml27
-rw-r--r--users/app/views/users/_edit.html.haml74
-rw-r--r--users/app/views/users/_user.html.haml2
-rw-r--r--users/app/views/users/show.html.haml23
23 files changed, 394 insertions, 146 deletions
diff --git a/users/app/assets/javascripts/srp b/users/app/assets/javascripts/srp
-Subproject d22bf3b9fe2fd31192e1e1b358e97e5a0f3f90b
+Subproject 8f33d32d40b1e21ae7fb9a92c78a275422af421
diff --git a/users/app/assets/javascripts/users.js b/users/app/assets/javascripts/users.js
index aaeba6e..8486756 100644
--- a/users/app/assets/javascripts/users.js
+++ b/users/app/assets/javascripts/users.js
@@ -46,6 +46,13 @@
$(form).find('input[type="submit"]').button('loading');
};
+ resetButtons = function(submitEvent) {
+ var form = $('form.submitted')
+ // bootstrap loading state:
+ $(form).find('input[type="submit"]').button('reset');
+ $(form).removeClass('submitted')
+ };
+
//
// PUBLIC FUNCTIONS
//
@@ -70,24 +77,36 @@
//
srp.error = function(message) {
clear_errors();
- var element, error, field;
+ var errors = extractErrors(message);
+ displayErrors(errors);
+ resetButtons();
+ }
+
+ function extractErrors(message) {
if ($.isPlainObject(message) && message.errors) {
- for (field in message.errors) {
- if (field == 'base') {
- alert_message(message.errors[field]);
- continue;
- }
- error = message.errors[field];
- element = $('form input[name$="[' + field + ']"]');
- if (!element) {
- continue;
- }
- element.trigger('element:validate:fail.ClientSideValidations', error).data('valid', false);
- }
- } else if (message.error) {
- alert_message(message.error);
+ return message.errors;
} else {
- alert_message(JSON.stringify(message));
+ return {
+ base: (message.error || JSON.stringify(message))
+ };
+ }
+ }
+
+ function displayErrors(errors) {
+ for (var field in errors) {
+ var error = errors[field];
+ if (field === 'base') {
+ alert_message(error);
+ } else {
+ displayFieldError(field, error);
+ }
+ }
+ }
+
+ function displayFieldError(field, error) {
+ var element = $('form input[name$="[' + field + ']"]');
+ if (element) {
+ element.trigger('element:validate:fail.ClientSideValidations', error).data('valid', false);
}
};
diff --git a/users/app/controllers/keys_controller.rb b/users/app/controllers/keys_controller.rb
new file mode 100644
index 0000000..fb28901
--- /dev/null
+++ b/users/app/controllers/keys_controller.rb
@@ -0,0 +1,18 @@
+class KeysController < ApplicationController
+
+ #
+ # Render the user's key as plain text, without a layout.
+ #
+ # We will show blank page if user doesn't have key (which shouldn't generally occur)
+ # and a 404 error if user doesn't exist
+ #
+ def show
+ user = User.find_by_login(params[:login])
+ if user
+ render text: user.public_key, content_type: 'text/text'
+ else
+ raise ActionController::RoutingError.new('Not Found')
+ end
+ end
+
+end
diff --git a/users/app/controllers/overviews_controller.rb b/users/app/controllers/overviews_controller.rb
deleted file mode 100644
index 52ce267..0000000
--- a/users/app/controllers/overviews_controller.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class OverviewsController < UsersBaseController
-
- before_filter :authorize
- before_filter :fetch_user
-
- def show
- end
-
-end
diff --git a/users/app/controllers/sessions_controller.rb b/users/app/controllers/sessions_controller.rb
index 0494b51..ca228c2 100644
--- a/users/app/controllers/sessions_controller.rb
+++ b/users/app/controllers/sessions_controller.rb
@@ -1,6 +1,7 @@
class SessionsController < ApplicationController
def new
+ redirect_to root_path if logged_in?
@session = Session.new
if authentication_errors
@errors = authentication_errors
@@ -14,12 +15,12 @@ class SessionsController < ApplicationController
end
#
- # this is a bad hack, but user_overview_url(user) is not available
+ # this is a bad hack, but user_url(user) is not available
# also, this doesn't work because the redirect happens as a PUT. no idea why.
#
#Warden::Manager.after_authentication do |user, auth, opts|
# response = Rack::Response.new
- # response.redirect "/users/#{user.id}/overview"
+ # response.redirect "/users/#{user.id}"
# throw :warden, response.finish
#end
diff --git a/users/app/controllers/users_controller.rb b/users/app/controllers/users_controller.rb
index f66277d..0b32ec7 100644
--- a/users/app/controllers/users_controller.rb
+++ b/users/app/controllers/users_controller.rb
@@ -13,7 +13,7 @@ class UsersController < UsersBaseController
def index
if params[:query]
if @user = User.find_by_login(params[:query])
- redirect_to user_overview_url(@user)
+ redirect_to @user
return
else
@users = User.by_login.startkey(params[:query]).endkey(params[:query].succ)
@@ -34,6 +34,12 @@ class UsersController < UsersBaseController
def edit
end
+ ## added so updating service level works, but not sure we will actually want this. also not sure that this is place to prevent user from updating own effective service level, but here as placeholder:
+ def update
+ @user.update_attributes(params[:user]) unless (!admin? and params[:user][:effective_service_level])
+ respond_with @user
+ end
+
def deactivate
@user.enabled = false
@user.save
@@ -47,8 +53,16 @@ class UsersController < UsersBaseController
end
def destroy
- @user.destroy
- redirect_to admin? ? users_url : root_url
+ @user.account.destroy
+ flash[:notice] = I18n.t(:account_destroyed)
+ # admins can destroy other users
+ if @user != current_user
+ redirect_to users_url
+ else
+ # let's remove the invalid session
+ logout
+ redirect_to root_url
+ end
end
end
diff --git a/users/app/controllers/v1/users_controller.rb b/users/app/controllers/v1/users_controller.rb
index 03a5a62..0903888 100644
--- a/users/app/controllers/v1/users_controller.rb
+++ b/users/app/controllers/v1/users_controller.rb
@@ -24,15 +24,9 @@ module V1
end
def update
- account.update params[:user]
+ @user.account.update params[:user]
respond_with @user
end
- protected
-
- def account
- @user.account
- end
-
end
end
diff --git a/users/app/models/account.rb b/users/app/models/account.rb
index 5368a1b..cf998e4 100644
--- a/users/app/models/account.rb
+++ b/users/app/models/account.rb
@@ -1,5 +1,10 @@
#
-# A Composition of a User record and it's identity records.
+# The Account model takes care of the livecycle of a user.
+# It composes a User record and it's identity records.
+# It also allows for other engines to hook into the livecycle by
+# monkeypatching the create, update and destroy methods.
+# There's an ActiveSupport load_hook at the end of this file to
+# make this more easy.
#
class Account
@@ -22,16 +27,15 @@ class Account
@user.update_attributes attrs.slice(:password_verifier, :password_salt)
end
# TODO: move into identity controller
- update_pgp_key(attrs[:public_key]) if attrs.has_key? :public_key
+ key = update_pgp_key(attrs[:public_key])
+ @user.errors.set :public_key, key.errors.full_messages
@user.save && save_identities
@user.refresh_identity
end
def destroy
return unless @user
- Identity.by_user_id.key(@user.id).each do |identity|
- identity.destroy
- end
+ Identity.disable_all_for(@user)
@user.destroy
end
@@ -46,12 +50,19 @@ class Account
end
def update_pgp_key(key)
- @new_identity ||= Identity.for(@user)
- @new_identity.set_key(:pgp, key)
+ PgpKey.new(key).tap do |key|
+ if key.present? && key.valid?
+ @new_identity ||= Identity.for(@user)
+ @new_identity.set_key(:pgp, key)
+ end
+ end
end
def save_identities
@new_identity.try(:save) && @old_identity.try(:save)
end
+ # You can hook into the account lifecycle from different engines using
+ # ActiveSupport.on_load(:account) do ...
+ ActiveSupport.run_load_hooks(:account, self)
end
diff --git a/users/app/models/identity.rb b/users/app/models/identity.rb
index e0a24e9..cbb540e 100644
--- a/users/app/models/identity.rb
+++ b/users/app/models/identity.rb
@@ -27,6 +27,17 @@ class Identity < CouchRest::Model::Base
emit(doc.address, doc.keys["pgp"]);
}
EOJS
+ view :disabled,
+ map: <<-EOJS
+ function(doc) {
+ if (doc.type != 'Identity') {
+ return;
+ }
+ if (typeof doc.user_id === "undefined") {
+ emit(doc._id, 1);
+ }
+ }
+ EOJS
end
@@ -50,6 +61,19 @@ class Identity < CouchRest::Model::Base
identity
end
+ def self.disable_all_for(user)
+ Identity.by_user_id.key(user.id).each do |identity|
+ identity.disable
+ identity.save
+ end
+ end
+
+ def self.destroy_all_disabled
+ Identity.disabled.each do |identity|
+ identity.destroy
+ end
+ end
+
def self.attributes_from_user(user)
{ user_id: user.id,
address: user.email_address,
@@ -57,13 +81,22 @@ class Identity < CouchRest::Model::Base
}
end
+ def enabled?
+ self.destination && self.user_id
+ end
+
+ def disable
+ self.destination = nil
+ self.user_id = nil
+ end
+
def keys
read_attribute('keys') || HashWithIndifferentAccess.new
end
- def set_key(type, value)
- return if keys[type] == value
- write_attribute('keys', keys.merge(type => value))
+ def set_key(type, key)
+ return if keys[type] == key.to_s
+ write_attribute('keys', keys.merge(type => key.to_s))
end
# for LoginFormatValidation
@@ -93,7 +126,8 @@ class Identity < CouchRest::Model::Base
end
def destination_email
- return if destination.valid? #this ensures it is Email
+ return if destination.nil? # this identity is disabled
+ return if destination.valid? # this ensures it is Email
self.errors.add(:destination, destination.errors.messages[:email].first) #assumes only one error #TODO
end
diff --git a/users/app/models/local_email.rb b/users/app/models/local_email.rb
index 6303bb6..2b4c65e 100644
--- a/users/app/models/local_email.rb
+++ b/users/app/models/local_email.rb
@@ -1,5 +1,10 @@
class LocalEmail < Email
+ BLACKLIST_FROM_RFC2142 = [
+ 'postmaster', 'hostmaster', 'domainadmin', 'webmaster', 'www',
+ 'abuse', 'noc', 'security', 'usenet', 'news', 'uucp',
+ 'ftp', 'sales', 'marketing', 'support', 'info'
+ ]
def self.domain
APP_CONFIG[:domain]
@@ -11,6 +16,8 @@ class LocalEmail < Email
:message => "needs to end in @#{domain}"
}
+ validate :handle_allowed
+
def initialize(s)
super
append_domain_if_needed
@@ -32,4 +39,30 @@ class LocalEmail < Email
end
end
+ def handle_allowed
+ errors.add(:handle, "is reserved.") if handle_reserved?
+ end
+
+ def handle_reserved?
+ # *ARRAY in a case statement tests if ARRAY includes the handle.
+ case handle
+ when *APP_CONFIG[:handle_blacklist]
+ true
+ when *APP_CONFIG[:handle_whitelist]
+ false
+ when *BLACKLIST_FROM_RFC2142
+ true
+ else
+ handle_in_passwd?
+ end
+ end
+
+ def handle_in_passwd?
+ begin
+ !!Etc.getpwnam(handle)
+ rescue ArgumentError
+ # handle was not found
+ return false
+ end
+ end
end
diff --git a/users/app/models/pgp_key.rb b/users/app/models/pgp_key.rb
new file mode 100644
index 0000000..66f8660
--- /dev/null
+++ b/users/app/models/pgp_key.rb
@@ -0,0 +1,48 @@
+class PgpKey
+ include ActiveModel::Validations
+
+ KEYBLOCK_IDENTIFIERS = [
+ '-----BEGIN PGP PUBLIC KEY BLOCK-----',
+ '-----END PGP PUBLIC KEY BLOCK-----',
+ ]
+
+ # mostly for testing.
+ attr_accessor :keyblock
+
+ validate :validate_keyblock_format
+
+ def initialize(keyblock = nil)
+ @keyblock = keyblock
+ end
+
+ def to_s
+ @keyblock
+ end
+
+ def present?
+ @keyblock.present?
+ end
+
+ # allow comparison with plain keyblock strings.
+ def ==(other)
+ self.equal?(other) or
+ # relax the comparison on line ends.
+ self.to_s.tr_s("\n\r", '') == other.tr_s("\r\n", '')
+ end
+
+ protected
+
+ def validate_keyblock_format
+ if keyblock_identifier_missing?
+ errors.add :public_key_block,
+ "does not look like an armored pgp public key block"
+ end
+ end
+
+ def keyblock_identifier_missing?
+ KEYBLOCK_IDENTIFIERS.find do |identify|
+ !@keyblock.include?(identify)
+ end
+ end
+
+end
diff --git a/users/app/models/service_level.rb b/users/app/models/service_level.rb
new file mode 100644
index 0000000..299aaf1
--- /dev/null
+++ b/users/app/models/service_level.rb
@@ -0,0 +1,19 @@
+class ServiceLevel
+
+ def initialize(attributes = {})
+ @id = attributes[:id] || APP_CONFIG[:default_service_level]
+ end
+
+ def self.authenticated_select_options
+ APP_CONFIG[:service_levels].map { |id,config_hash| [config_hash[:description], id] if config_hash[:name] != 'anonymous'}.compact
+ end
+
+ def id
+ @id
+ end
+
+ def config_hash
+ APP_CONFIG[:service_levels][@id]
+ end
+
+end
diff --git a/users/app/models/token.rb b/users/app/models/token.rb
index dd87344..001eb40 100644
--- a/users/app/models/token.rb
+++ b/users/app/models/token.rb
@@ -11,6 +11,25 @@ class Token < CouchRest::Model::Base
validates :user_id, presence: true
+ design do
+ view :by_last_seen_at
+ end
+
+ def self.expires_after
+ APP_CONFIG[:auth] && APP_CONFIG[:auth][:token_expires_after]
+ end
+
+ def self.expired
+ return [] unless expires_after
+ by_last_seen_at.endkey(expires_after.minutes.ago)
+ end
+
+ def self.destroy_all_expired
+ self.expired.each do |token|
+ token.destroy
+ end
+ end
+
def authenticate
if expired?
destroy
@@ -27,21 +46,16 @@ class Token < CouchRest::Model::Base
end
def expired?
- expires_after and
- last_seen_at + expires_after.minutes < Time.now
- end
-
- def expires_after
- APP_CONFIG[:auth] && APP_CONFIG[:auth][:token_expires_after]
+ Token.expires_after and
+ last_seen_at < Token.expires_after.minutes.ago
end
def initialize(*args)
super
- self.id = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '')
- self.last_seen_at = Time.now
- end
-
- design do
+ if new_record?
+ self.id = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '')
+ self.last_seen_at = Time.now
+ end
end
end
diff --git a/users/app/models/unauthenticated_user.rb b/users/app/models/unauthenticated_user.rb
index 99a6874..0fc17d2 100644
--- a/users/app/models/unauthenticated_user.rb
+++ b/users/app/models/unauthenticated_user.rb
@@ -1,4 +1,6 @@
# The nil object for the user class
class UnauthenticatedUser < Object
+ # will probably want something here to return service level as APP_CONFIG[:service_levels][0] but not sure how will be accessing.
+
end
diff --git a/users/app/models/user.rb b/users/app/models/user.rb
index a14fcb5..720f5a9 100644
--- a/users/app/models/user.rb
+++ b/users/app/models/user.rb
@@ -9,6 +9,12 @@ class User < CouchRest::Model::Base
property :enabled, TrueClass, :default => true
+ # these will be null by default but we shouldn't ever pull them directly, but only via the methods that will return the full ServiceLevel
+ property :desired_service_level_code, Integer, :accessible => true
+ property :effective_service_level_code, Integer, :accessible => true
+
+ before_save :update_effective_service_level
+
validates :login, :password_salt, :password_verifier,
:presence => true
@@ -94,6 +100,16 @@ class User < CouchRest::Model::Base
@identity = Identity.for(self)
end
+ def desired_service_level
+ code = self.desired_service_level_code || APP_CONFIG[:default_service_level]
+ ServiceLevel.new({id: code})
+ end
+
+ def effective_service_level
+ code = self.effective_service_level_code || self.desired_service_level.id
+ ServiceLevel.new({id: code})
+ end
+
protected
##
@@ -116,4 +132,12 @@ class User < CouchRest::Model::Base
def serverside?
true
end
+
+ def update_effective_service_level
+ # TODO: Is this always the case? Might there be a situation where the admin has set the effective service level and we don't want it changed to match the desired one?
+ if self.desired_service_level_code_changed?
+ self.effective_service_level_code = self.desired_service_level_code
+ end
+ end
+
end
diff --git a/users/app/views/overviews/show.html.haml b/users/app/views/overviews/show.html.haml
deleted file mode 100644
index d3409df..0000000
--- a/users/app/views/overviews/show.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-.overview
-
- %h2.first= t(:overview_welcome, :username => @user.login)
-
- - if admin?
- %p
- = t(:created)
- = @user.created_at
- %br
- = t(:updated)
- = @user.updated_at
- %br
- = t(:enabled)
- = @user.enabled?
-
- %p= t(:overview_intro)
-
- %ul.unstyled
- %li= icon('user') + link_to(t(:overview_account), edit_user_path(@user))
- - # %li= icon('envelope') + link_to(t(:overview_email), {insert path for user identities, presuambly}
- %li= icon('question-sign') + link_to(t(:overview_tickets), user_tickets_path(@user))
- %li= icon('shopping-cart') + link_to(t(:overview_billing), show_or_new_customer_link(@user)) if APP_CONFIG[:payment].present?
diff --git a/users/app/views/users/_change_password.html.haml b/users/app/views/users/_change_password.html.haml
new file mode 100644
index 0000000..425e3ee
--- /dev/null
+++ b/users/app/views/users/_change_password.html.haml
@@ -0,0 +1,21 @@
+-#
+-# CHANGE PASSWORD
+-#
+-# * everything about this form is handled with javascript. So take care when changing any ids.
+-# * the login is required when changing the password because it is used as part of the salt when calculating the password verifier.
+-# however, we don't want the user to change their login without generating a new key, so we hide the ui for this
+-# (although it works perfectly fine to change username if the field was visible).
+-#
+
+- form_options = {:url => '/not-used', :html => {:class => user_form_class('form-horizontal'), :id => 'update_login_and_password', :data => {token: session[:token]}}, :validate => true}
+= simple_form_for @user, form_options do |f|
+ %legend= t(:change_password)
+ = hidden_field_tag 'user_param', @user.to_param
+ .hidden
+ = f.input :login, :label => t(:username), :required => false, :input_html => {:id => :srp_username}
+ = f.input :password, :required => false, :validate => true, :input_html => { :id => :srp_password }
+ = f.input :password_confirmation, :required => false, :input_html => { :id => :srp_password_confirmation }
+ .control-group
+ .controls
+ = f.submit t(:save), :class => 'btn btn-primary'
+
diff --git a/users/app/views/users/_change_pgp_key.html.haml b/users/app/views/users/_change_pgp_key.html.haml
new file mode 100644
index 0000000..e465125
--- /dev/null
+++ b/users/app/views/users/_change_pgp_key.html.haml
@@ -0,0 +1,13 @@
+-#
+-# CHANGE PGP KEY
+-#
+-# this will be replaced by a identities controller/view at some point
+-#
+
+- form_options = {:html => {:class => user_form_class('form-horizontal'), :id => 'update_pgp_key', :data => {token: session[:token]}}, :validate => true}
+= simple_form_for [:api, @user], form_options do |f|
+ %legend= t(:advanced_options)
+ = f.input :public_key, :as => :text, :hint => t(:use_ascii_key), :input_html => {:class => "full-width", :rows => 4}
+ .control-group
+ .controls
+ = f.submit t(:save), :class => 'btn', :data => {"loading-text" => "Saving..."}
diff --git a/users/app/views/users/_change_service_level.html.haml b/users/app/views/users/_change_service_level.html.haml
new file mode 100644
index 0000000..61e67d9
--- /dev/null
+++ b/users/app/views/users/_change_service_level.html.haml
@@ -0,0 +1,18 @@
+-# TODO: probably won't want here, but here for now. Also, we will need way to ensure payment if they pick a non-free plan.
+-#
+-# SERVICE LEVEL
+-#
+- if APP_CONFIG[:service_levels]
+ - form_options = {:html => {:class => user_form_class('form-horizontal'), :id => 'update_service_level', :data => {token: session[:token]}}, :validate => true}
+ = simple_form_for @user, form_options do |f|
+ %legend= t(:service_level)
+ - if @user != current_user
+ = t(:desired_service_level)
+ = f.select :desired_service_level_code, ServiceLevel.authenticated_select_options, :selected => @user.desired_service_level.id
+ - if @user != current_user
+ %p
+ = t(:effective_service_level)
+ = f.select :effective_service_level_code, ServiceLevel.authenticated_select_options, :selected => @user.effective_service_level.id
+ .control-group
+ .controls
+ = f.submit t(:save), :class => 'btn', :data => {"loading-text" => "Saving..."}
diff --git a/users/app/views/users/_destroy_account.html.haml b/users/app/views/users/_destroy_account.html.haml
new file mode 100644
index 0000000..445f3c4
--- /dev/null
+++ b/users/app/views/users/_destroy_account.html.haml
@@ -0,0 +1,27 @@
+-#
+-# DESTROY ACCOUNT
+-#
+
+%legend
+ - if @user == current_user
+ = t(:destroy_my_account)
+ - else
+ = t(:admin_destroy_account, :username => @user.login)
+%p= t(:destroy_account_info)
+= link_to user_path(@user), :method => :delete, :confirm => t(:are_you_sure), :class => "btn btn-danger" do
+ %i.icon-remove.icon-white
+ = t(:destroy_my_account)
+- if @user != current_user and @user.enabled?
+ %legend
+ = t(:deactivate_account, :username => @user.login)
+ %p= t(:deactivate_description)
+ = link_to deactivate_user_path(@user), :method => :post, :class => "btn btn-warning" do
+ %i.icon-pause.icon-white
+ = t(:deactivate)
+- elsif @user != current_user and !@user.enabled?
+ %legend
+ = t(:enable_account, :username => @user.login)
+ %p= t(:enable_description)
+ = link_to enable_user_path(@user), :method => :post, :class => "btn btn-warning" do
+ %i.icon-ok.icon-white
+ = t(:enable)
diff --git a/users/app/views/users/_edit.html.haml b/users/app/views/users/_edit.html.haml
index 9d2473b..1d2b68a 100644
--- a/users/app/views/users/_edit.html.haml
+++ b/users/app/views/users/_edit.html.haml
@@ -1,66 +1,14 @@
-#
-# edit user form, used by both show and edit actions.
-#
-
--#
--# CHANGE PASSWORD
--#
--# * everything about this form is handled with javascript. So take care when changing any ids.
--# * the login is required when changing the password because it is used as part of the salt when calculating the password verifier.
--# however, we don't want the user to change their login without generating a new key, so we hide the ui for this
--# (although it works perfectly fine to change username if the field was visible).
--#
-
-- form_options = {:url => '/not-used', :html => {:class => user_form_class('form-horizontal'), :id => 'update_login_and_password', :data => {token: session[:token]}}, :validate => true}
-= simple_form_for @user, form_options do |f|
- %legend= t(:change_password)
- = hidden_field_tag 'user_param', @user.to_param
- .hidden
- = f.input :login, :label => t(:username), :required => false, :input_html => {:id => :srp_username}
- = f.input :password, :required => false, :validate => true, :input_html => { :id => :srp_password }
- = f.input :password_confirmation, :required => false, :input_html => { :id => :srp_password_confirmation }
- .control-group
- .controls
- = f.submit t(:save), :class => 'btn btn-primary'
-
--#
--# CHANGE PGP KEY
--#
--# this will be replaced by a identities controller/view at some point
--#
-
-- form_options = {:html => {:class => user_form_class('form-horizontal'), :id => 'update_pgp_key', :data => {token: session[:token]}}, :validate => true}
-= simple_form_for [:api, @user], form_options do |f|
- %legend= t(:advanced_options)
- = f.input :public_key, :as => :text, :hint => t(:use_ascii_key), :input_html => {:class => "full-width", :rows => 4}
- .control-group
- .controls
- = f.submit t(:save), :class => 'btn', :data => {"loading-text" => "Saving..."}
-
--#
--# DESTROY ACCOUNT
--#
-
-%legend
- - if @user == current_user
- = t(:destroy_my_account)
- - else
- = t(:admin_destroy_account, :username => @user.login)
-%p= t(:destroy_account_info)
-= link_to user_path(@user), :method => :delete, :confirm => t(:are_you_sure), :class => "btn btn-danger" do
- %i.icon-remove.icon-white
- = t(:destroy_my_account)
-- if @user != current_user and @user.enabled?
- %legend
- = t(:deactivate_account, :username => @user.login)
- %p= t(:deactivate_description)
- = link_to deactivate_user_path(@user), :method => :post, :class => "btn btn-warning" do
- %i.icon-pause.icon-white
- = t(:deactivate)
-- elsif @user != current_user and !@user.enabled?
- %legend
- = t(:enable_account, :username => @user.login)
- %p= t(:enable_description)
- = link_to enable_user_path(@user), :method => :post, :class => "btn btn-warning" do
- %i.icon-ok.icon-white
- = t(:enable)
+-# We render a bunch of forms here. Which we use depends upon config settings
+-# user_actions and admin_actions. They both include an array of actions
+-# allowed to users and admins.
+-# Possible forms are:
+-# 'change_password'
+-# 'change_pgp_key'
+-# 'change_service_level'
+-# 'destroy_account'
+- actions = APP_CONFIG[admin? ? :admin_actions : :user_actions] || []
+- actions.each do |action|
+ = render action
diff --git a/users/app/views/users/_user.html.haml b/users/app/views/users/_user.html.haml
index 990d9cf..583d22f 100644
--- a/users/app/views/users/_user.html.haml
+++ b/users/app/views/users/_user.html.haml
@@ -1,4 +1,4 @@
%tr
- %td= link_to user.login, user_overview_path(user)
+ %td= link_to user.login, user
%td= l(user.created_at, :format => :short)
%td= l(user.updated_at, :format => :short)
diff --git a/users/app/views/users/show.html.haml b/users/app/views/users/show.html.haml
index 434c025..7bea370 100644
--- a/users/app/views/users/show.html.haml
+++ b/users/app/views/users/show.html.haml
@@ -1 +1,22 @@
-= render 'edit'
+.overview
+
+ %h2.first= t(:overview_welcome, :username => @user.login)
+
+ - if admin?
+ %p
+ = t(:created)
+ = @user.created_at
+ %br
+ = t(:updated)
+ = @user.updated_at
+ %br
+ = t(:enabled)
+ = @user.enabled?
+
+ %p= t(:overview_intro)
+
+ %ul.unstyled
+ %li= icon('user') + link_to(t(:overview_account), edit_user_path(@user))
+ - # %li= icon('envelope') + link_to(t(:overview_email), {insert path for user identities, presuambly}
+ %li= icon('question-sign') + link_to(t(:overview_tickets), user_tickets_path(@user))
+ %li= icon('shopping-cart') + link_to(t(:overview_billing), billing_top_link(@user)) if APP_CONFIG[:payment].present?