From b6d14dc19dd350a807826e3e097738a36613e083 Mon Sep 17 00:00:00 2001 From: Azul Date: Tue, 8 Apr 2014 11:49:14 +0200 Subject: moving users: app and test files --- app/assets/images/leap_web_users/.gitkeep | 0 app/assets/javascripts/leap_web_users/.gitkeep | 0 app/assets/javascripts/users.js | 132 +++++++++++++++ app/assets/stylesheets/leap_web_users/.gitkeep | 0 app/controllers/.gitkeep | 0 app/controllers/account_settings_controller.rb | 0 .../controller_extension/authentication.rb | 75 +++++++++ .../controller_extension/token_authentication.rb | 27 ++++ app/controllers/keys_controller.rb | 18 +++ app/controllers/sessions_controller.rb | 28 ++++ app/controllers/users_base_controller.rb | 18 +++ app/controllers/users_controller.rb | 69 ++++++++ app/controllers/v1/messages_controller.rb | 25 +++ app/controllers/v1/sessions_controller.rb | 45 ++++++ app/controllers/v1/users_controller.rb | 32 ++++ app/controllers/webfinger_controller.rb | 19 +++ app/designs/message/by_user_ids_to_show.js | 7 + .../message/by_user_ids_to_show_and_created_at.js | 9 ++ ...by_created_at_and_one_month_warning_not_sent.js | 5 + app/helpers/.gitkeep | 0 app/helpers/email_aliases_helper.rb | 11 ++ app/helpers/sessions_helper.rb | 2 + app/helpers/users_helper.rb | 14 ++ app/models/account.rb | 68 ++++++++ app/models/email.rb | 26 +++ app/models/identity.rb | 136 ++++++++++++++++ app/models/local_email.rb | 68 ++++++++ app/models/login_format_validation.rb | 21 +++ app/models/message.rb | 29 ++++ app/models/pgp_key.rb | 48 ++++++ app/models/service_level.rb | 19 +++ app/models/session.rb | 32 ++++ app/models/token.rb | 69 ++++++++ app/models/unauthenticated_user.rb | 6 + app/models/user.rb | 179 +++++++++++++++++++++ app/views/.gitkeep | 0 app/views/emails/_email.html.haml | 6 + app/views/sessions/new.html.haml | 10 ++ app/views/sessions/new.json.erb | 3 + app/views/users/_change_password.html.haml | 21 +++ app/views/users/_change_pgp_key.html.haml | 13 ++ app/views/users/_change_service_level.html.haml | 18 +++ app/views/users/_destroy_account.html.haml | 27 ++++ app/views/users/_edit.html.haml | 14 ++ app/views/users/_user.html.haml | 4 + app/views/users/_warnings.html.haml | 12 ++ app/views/users/edit.html.haml | 1 + app/views/users/index.html.haml | 13 ++ app/views/users/new.html.haml | 19 +++ app/views/users/show.html.haml | 25 +++ app/views/v1/sessions/new.json.erb | 3 + app/views/webfinger/host_meta.xml.erb | 11 ++ app/views/webfinger/search.xml.erb | 7 + test/factories.rb | 34 ++++ test/functional/application_controller_test.rb | 28 ++++ test/functional/helper_methods_test.rb | 39 +++++ test/functional/keys_controller_test.rb | 32 ++++ test/functional/sessions_controller_test.rb | 59 +++++++ test/functional/test_helpers_test.rb | 38 +++++ test/functional/users_controller_test.rb | 165 +++++++++++++++++++ test/functional/v1/messages_controller_test.rb | 57 +++++++ test/functional/v1/sessions_controller_test.rb | 62 +++++++ test/functional/v1/users_controller_test.rb | 74 +++++++++ test/functional/webfinger_controller_test.rb | 33 ++++ test/integration/api/Readme.md | 23 +++ test/integration/api/login_test.rb | 50 ++++++ test/integration/api/pgp_key_test.rb | 35 ++++ test/integration/api/python/flow_with_srp.py | 96 +++++++++++ .../integration/api/python/login_wrong_username.py | 19 +++ test/integration/api/python/signup.py | 20 +++ test/integration/api/python/signup_and_login.py | 44 +++++ .../api/python/signup_and_login_wrong_password.py | 43 +++++ test/integration/api/python/umlauts.py | 79 +++++++++ test/integration/api/signup_test.rb | 20 +++ test/integration/api/srp_test.rb | 104 ++++++++++++ test/integration/api/update_account_test.rb | 51 ++++++ test/integration/browser/account_test.rb | 147 +++++++++++++++++ test/integration/browser/session_test.rb | 27 ++++ test/integration/navigation_test.rb | 9 ++ test/leap_web_users_test.rb | 7 + test/support/auth_test_helper.rb | 65 ++++++++ test/support/stub_record_helper.rb | 53 ++++++ test/support/time_test_helper.rb | 30 ++++ test/unit/account_test.rb | 47 ++++++ test/unit/helpers/session_helper_test.rb | 4 + test/unit/helpers/users_helper_test.rb | 4 + test/unit/identity_test.rb | 133 +++++++++++++++ test/unit/local_email_test.rb | 65 ++++++++ test/unit/token_test.rb | 89 ++++++++++ test/unit/unauthenticated_user_test.rb | 7 + test/unit/user_test.rb | 68 ++++++++ .../warden_strategy_secure_remote_password_test.rb | 63 ++++++++ test/unit/webfinger/host_meta_presenter_test.rb | 24 +++ test/unit/webfinger/user_presenter_test.rb | 49 ++++++ users/app/assets/images/leap_web_users/.gitkeep | 0 .../app/assets/javascripts/leap_web_users/.gitkeep | 0 users/app/assets/javascripts/srp | 1 - users/app/assets/javascripts/users.js | 132 --------------- .../app/assets/stylesheets/leap_web_users/.gitkeep | 0 users/app/controllers/.gitkeep | 0 .../app/controllers/account_settings_controller.rb | 0 .../controller_extension/authentication.rb | 75 --------- .../controller_extension/token_authentication.rb | 27 ---- users/app/controllers/keys_controller.rb | 18 --- users/app/controllers/sessions_controller.rb | 28 ---- users/app/controllers/users_base_controller.rb | 18 --- users/app/controllers/users_controller.rb | 69 -------- users/app/controllers/v1/messages_controller.rb | 25 --- users/app/controllers/v1/sessions_controller.rb | 45 ------ users/app/controllers/v1/users_controller.rb | 32 ---- users/app/controllers/webfinger_controller.rb | 19 --- users/app/designs/message/by_user_ids_to_show.js | 7 - .../message/by_user_ids_to_show_and_created_at.js | 9 -- ...by_created_at_and_one_month_warning_not_sent.js | 5 - users/app/helpers/.gitkeep | 0 users/app/helpers/email_aliases_helper.rb | 11 -- users/app/helpers/sessions_helper.rb | 2 - users/app/helpers/users_helper.rb | 14 -- users/app/mailers/.gitkeep | 0 users/app/models/.gitkeep | 0 users/app/models/account.rb | 68 -------- users/app/models/email.rb | 26 --- users/app/models/identity.rb | 136 ---------------- users/app/models/local_email.rb | 68 -------- users/app/models/login_format_validation.rb | 21 --- users/app/models/message.rb | 29 ---- users/app/models/pgp_key.rb | 48 ------ users/app/models/service_level.rb | 19 --- users/app/models/session.rb | 32 ---- users/app/models/token.rb | 69 -------- users/app/models/unauthenticated_user.rb | 6 - users/app/models/user.rb | 179 --------------------- users/app/views/.gitkeep | 0 users/app/views/emails/_email.html.haml | 6 - users/app/views/sessions/new.html.haml | 10 -- users/app/views/sessions/new.json.erb | 3 - users/app/views/users/_change_password.html.haml | 21 --- users/app/views/users/_change_pgp_key.html.haml | 13 -- .../views/users/_change_service_level.html.haml | 18 --- users/app/views/users/_destroy_account.html.haml | 27 ---- users/app/views/users/_edit.html.haml | 14 -- users/app/views/users/_user.html.haml | 4 - users/app/views/users/_warnings.html.haml | 12 -- users/app/views/users/edit.html.haml | 1 - users/app/views/users/index.html.haml | 13 -- users/app/views/users/new.html.haml | 19 --- users/app/views/users/show.html.haml | 25 --- users/app/views/v1/sessions/new.json.erb | 3 - users/app/views/webfinger/host_meta.xml.erb | 11 -- users/app/views/webfinger/search.xml.erb | 7 - users/test/factories.rb | 34 ---- users/test/fixtures/.gitkeep | 0 users/test/functional/.gitkeep | 0 .../test/functional/application_controller_test.rb | 28 ---- users/test/functional/helper_methods_test.rb | 39 ----- users/test/functional/keys_controller_test.rb | 32 ---- users/test/functional/sessions_controller_test.rb | 59 ------- users/test/functional/test_helpers_test.rb | 38 ----- users/test/functional/users_controller_test.rb | 165 ------------------- .../test/functional/v1/messages_controller_test.rb | 57 ------- .../test/functional/v1/sessions_controller_test.rb | 62 ------- users/test/functional/v1/users_controller_test.rb | 74 --------- users/test/functional/webfinger_controller_test.rb | 33 ---- users/test/integration/.gitkeep | 0 users/test/integration/api/Readme.md | 23 --- users/test/integration/api/login_test.rb | 50 ------ users/test/integration/api/pgp_key_test.rb | 35 ---- users/test/integration/api/python/flow_with_srp.py | 96 ----------- .../integration/api/python/login_wrong_username.py | 19 --- users/test/integration/api/python/signup.py | 20 --- .../integration/api/python/signup_and_login.py | 44 ----- .../api/python/signup_and_login_wrong_password.py | 43 ----- users/test/integration/api/python/umlauts.py | 79 --------- users/test/integration/api/signup_test.rb | 20 --- users/test/integration/api/srp_test.rb | 104 ------------ users/test/integration/api/update_account_test.rb | 51 ------ users/test/integration/browser/account_test.rb | 147 ----------------- users/test/integration/browser/session_test.rb | 27 ---- users/test/integration/navigation_test.rb | 9 -- users/test/leap_web_users_test.rb | 7 - users/test/support/auth_test_helper.rb | 65 -------- users/test/support/stub_record_helper.rb | 53 ------ users/test/support/time_test_helper.rb | 30 ---- users/test/test_helper.rb | 9 -- users/test/unit/.gitkeep | 0 users/test/unit/account_test.rb | 47 ------ users/test/unit/email_test.rb | 19 --- users/test/unit/helpers/session_helper_test.rb | 4 - users/test/unit/helpers/users_helper_test.rb | 4 - users/test/unit/identity_test.rb | 133 --------------- users/test/unit/local_email_test.rb | 65 -------- users/test/unit/token_test.rb | 89 ---------- users/test/unit/unauthenticated_user_test.rb | 7 - users/test/unit/user_test.rb | 68 -------- .../warden_strategy_secure_remote_password_test.rb | 63 -------- .../unit/webfinger/host_meta_presenter_test.rb | 24 --- users/test/unit/webfinger/user_presenter_test.rb | 49 ------ 197 files changed, 3540 insertions(+), 3569 deletions(-) create mode 100644 app/assets/images/leap_web_users/.gitkeep create mode 100644 app/assets/javascripts/leap_web_users/.gitkeep create mode 100644 app/assets/javascripts/users.js create mode 100644 app/assets/stylesheets/leap_web_users/.gitkeep create mode 100644 app/controllers/.gitkeep create mode 100644 app/controllers/account_settings_controller.rb create mode 100644 app/controllers/controller_extension/authentication.rb create mode 100644 app/controllers/controller_extension/token_authentication.rb create mode 100644 app/controllers/keys_controller.rb create mode 100644 app/controllers/sessions_controller.rb create mode 100644 app/controllers/users_base_controller.rb create mode 100644 app/controllers/users_controller.rb create mode 100644 app/controllers/v1/messages_controller.rb create mode 100644 app/controllers/v1/sessions_controller.rb create mode 100644 app/controllers/v1/users_controller.rb create mode 100644 app/controllers/webfinger_controller.rb create mode 100644 app/designs/message/by_user_ids_to_show.js create mode 100644 app/designs/message/by_user_ids_to_show_and_created_at.js create mode 100644 app/designs/user/by_created_at_and_one_month_warning_not_sent.js create mode 100644 app/helpers/.gitkeep create mode 100644 app/helpers/email_aliases_helper.rb create mode 100644 app/helpers/sessions_helper.rb create mode 100644 app/helpers/users_helper.rb create mode 100644 app/models/account.rb create mode 100644 app/models/email.rb create mode 100644 app/models/identity.rb create mode 100644 app/models/local_email.rb create mode 100644 app/models/login_format_validation.rb create mode 100644 app/models/message.rb create mode 100644 app/models/pgp_key.rb create mode 100644 app/models/service_level.rb create mode 100644 app/models/session.rb create mode 100644 app/models/token.rb create mode 100644 app/models/unauthenticated_user.rb create mode 100644 app/models/user.rb create mode 100644 app/views/.gitkeep create mode 100644 app/views/emails/_email.html.haml create mode 100644 app/views/sessions/new.html.haml create mode 100644 app/views/sessions/new.json.erb create mode 100644 app/views/users/_change_password.html.haml create mode 100644 app/views/users/_change_pgp_key.html.haml create mode 100644 app/views/users/_change_service_level.html.haml create mode 100644 app/views/users/_destroy_account.html.haml create mode 100644 app/views/users/_edit.html.haml create mode 100644 app/views/users/_user.html.haml create mode 100644 app/views/users/_warnings.html.haml create mode 100644 app/views/users/edit.html.haml create mode 100644 app/views/users/index.html.haml create mode 100644 app/views/users/new.html.haml create mode 100644 app/views/users/show.html.haml create mode 100644 app/views/v1/sessions/new.json.erb create mode 100644 app/views/webfinger/host_meta.xml.erb create mode 100644 app/views/webfinger/search.xml.erb create mode 100644 test/functional/application_controller_test.rb create mode 100644 test/functional/helper_methods_test.rb create mode 100644 test/functional/keys_controller_test.rb create mode 100644 test/functional/sessions_controller_test.rb create mode 100644 test/functional/test_helpers_test.rb create mode 100644 test/functional/users_controller_test.rb create mode 100644 test/functional/v1/messages_controller_test.rb create mode 100644 test/functional/v1/sessions_controller_test.rb create mode 100644 test/functional/v1/users_controller_test.rb create mode 100644 test/functional/webfinger_controller_test.rb create mode 100644 test/integration/api/Readme.md create mode 100644 test/integration/api/login_test.rb create mode 100644 test/integration/api/pgp_key_test.rb create mode 100755 test/integration/api/python/flow_with_srp.py create mode 100755 test/integration/api/python/login_wrong_username.py create mode 100755 test/integration/api/python/signup.py create mode 100755 test/integration/api/python/signup_and_login.py create mode 100755 test/integration/api/python/signup_and_login_wrong_password.py create mode 100755 test/integration/api/python/umlauts.py create mode 100644 test/integration/api/signup_test.rb create mode 100644 test/integration/api/srp_test.rb create mode 100644 test/integration/api/update_account_test.rb create mode 100644 test/integration/browser/account_test.rb create mode 100644 test/integration/browser/session_test.rb create mode 100644 test/integration/navigation_test.rb create mode 100644 test/leap_web_users_test.rb create mode 100644 test/support/auth_test_helper.rb create mode 100644 test/support/stub_record_helper.rb create mode 100644 test/support/time_test_helper.rb create mode 100644 test/unit/account_test.rb create mode 100644 test/unit/helpers/session_helper_test.rb create mode 100644 test/unit/helpers/users_helper_test.rb create mode 100644 test/unit/identity_test.rb create mode 100644 test/unit/local_email_test.rb create mode 100644 test/unit/token_test.rb create mode 100644 test/unit/unauthenticated_user_test.rb create mode 100644 test/unit/user_test.rb create mode 100644 test/unit/warden_strategy_secure_remote_password_test.rb create mode 100644 test/unit/webfinger/host_meta_presenter_test.rb create mode 100644 test/unit/webfinger/user_presenter_test.rb delete mode 100644 users/app/assets/images/leap_web_users/.gitkeep delete mode 100644 users/app/assets/javascripts/leap_web_users/.gitkeep delete mode 160000 users/app/assets/javascripts/srp delete mode 100644 users/app/assets/javascripts/users.js delete mode 100644 users/app/assets/stylesheets/leap_web_users/.gitkeep delete mode 100644 users/app/controllers/.gitkeep delete mode 100644 users/app/controllers/account_settings_controller.rb delete mode 100644 users/app/controllers/controller_extension/authentication.rb delete mode 100644 users/app/controllers/controller_extension/token_authentication.rb delete mode 100644 users/app/controllers/keys_controller.rb delete mode 100644 users/app/controllers/sessions_controller.rb delete mode 100644 users/app/controllers/users_base_controller.rb delete mode 100644 users/app/controllers/users_controller.rb delete mode 100644 users/app/controllers/v1/messages_controller.rb delete mode 100644 users/app/controllers/v1/sessions_controller.rb delete mode 100644 users/app/controllers/v1/users_controller.rb delete mode 100644 users/app/controllers/webfinger_controller.rb delete mode 100644 users/app/designs/message/by_user_ids_to_show.js delete mode 100644 users/app/designs/message/by_user_ids_to_show_and_created_at.js delete mode 100644 users/app/designs/user/by_created_at_and_one_month_warning_not_sent.js delete mode 100644 users/app/helpers/.gitkeep delete mode 100644 users/app/helpers/email_aliases_helper.rb delete mode 100644 users/app/helpers/sessions_helper.rb delete mode 100644 users/app/helpers/users_helper.rb delete mode 100644 users/app/mailers/.gitkeep delete mode 100644 users/app/models/.gitkeep delete mode 100644 users/app/models/account.rb delete mode 100644 users/app/models/email.rb delete mode 100644 users/app/models/identity.rb delete mode 100644 users/app/models/local_email.rb delete mode 100644 users/app/models/login_format_validation.rb delete mode 100644 users/app/models/message.rb delete mode 100644 users/app/models/pgp_key.rb delete mode 100644 users/app/models/service_level.rb delete mode 100644 users/app/models/session.rb delete mode 100644 users/app/models/token.rb delete mode 100644 users/app/models/unauthenticated_user.rb delete mode 100644 users/app/models/user.rb delete mode 100644 users/app/views/.gitkeep delete mode 100644 users/app/views/emails/_email.html.haml delete mode 100644 users/app/views/sessions/new.html.haml delete mode 100644 users/app/views/sessions/new.json.erb delete mode 100644 users/app/views/users/_change_password.html.haml delete mode 100644 users/app/views/users/_change_pgp_key.html.haml delete mode 100644 users/app/views/users/_change_service_level.html.haml delete mode 100644 users/app/views/users/_destroy_account.html.haml delete mode 100644 users/app/views/users/_edit.html.haml delete mode 100644 users/app/views/users/_user.html.haml delete mode 100644 users/app/views/users/_warnings.html.haml delete mode 100644 users/app/views/users/edit.html.haml delete mode 100644 users/app/views/users/index.html.haml delete mode 100644 users/app/views/users/new.html.haml delete mode 100644 users/app/views/users/show.html.haml delete mode 100644 users/app/views/v1/sessions/new.json.erb delete mode 100644 users/app/views/webfinger/host_meta.xml.erb delete mode 100644 users/app/views/webfinger/search.xml.erb delete mode 100644 users/test/factories.rb delete mode 100644 users/test/fixtures/.gitkeep delete mode 100644 users/test/functional/.gitkeep delete mode 100644 users/test/functional/application_controller_test.rb delete mode 100644 users/test/functional/helper_methods_test.rb delete mode 100644 users/test/functional/keys_controller_test.rb delete mode 100644 users/test/functional/sessions_controller_test.rb delete mode 100644 users/test/functional/test_helpers_test.rb delete mode 100644 users/test/functional/users_controller_test.rb delete mode 100644 users/test/functional/v1/messages_controller_test.rb delete mode 100644 users/test/functional/v1/sessions_controller_test.rb delete mode 100644 users/test/functional/v1/users_controller_test.rb delete mode 100644 users/test/functional/webfinger_controller_test.rb delete mode 100644 users/test/integration/.gitkeep delete mode 100644 users/test/integration/api/Readme.md delete mode 100644 users/test/integration/api/login_test.rb delete mode 100644 users/test/integration/api/pgp_key_test.rb delete mode 100755 users/test/integration/api/python/flow_with_srp.py delete mode 100755 users/test/integration/api/python/login_wrong_username.py delete mode 100755 users/test/integration/api/python/signup.py delete mode 100755 users/test/integration/api/python/signup_and_login.py delete mode 100755 users/test/integration/api/python/signup_and_login_wrong_password.py delete mode 100755 users/test/integration/api/python/umlauts.py delete mode 100644 users/test/integration/api/signup_test.rb delete mode 100644 users/test/integration/api/srp_test.rb delete mode 100644 users/test/integration/api/update_account_test.rb delete mode 100644 users/test/integration/browser/account_test.rb delete mode 100644 users/test/integration/browser/session_test.rb delete mode 100644 users/test/integration/navigation_test.rb delete mode 100644 users/test/leap_web_users_test.rb delete mode 100644 users/test/support/auth_test_helper.rb delete mode 100644 users/test/support/stub_record_helper.rb delete mode 100644 users/test/support/time_test_helper.rb delete mode 100644 users/test/test_helper.rb delete mode 100644 users/test/unit/.gitkeep delete mode 100644 users/test/unit/account_test.rb delete mode 100644 users/test/unit/email_test.rb delete mode 100644 users/test/unit/helpers/session_helper_test.rb delete mode 100644 users/test/unit/helpers/users_helper_test.rb delete mode 100644 users/test/unit/identity_test.rb delete mode 100644 users/test/unit/local_email_test.rb delete mode 100644 users/test/unit/token_test.rb delete mode 100644 users/test/unit/unauthenticated_user_test.rb delete mode 100644 users/test/unit/user_test.rb delete mode 100644 users/test/unit/warden_strategy_secure_remote_password_test.rb delete mode 100644 users/test/unit/webfinger/host_meta_presenter_test.rb delete mode 100644 users/test/unit/webfinger/user_presenter_test.rb diff --git a/app/assets/images/leap_web_users/.gitkeep b/app/assets/images/leap_web_users/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/leap_web_users/.gitkeep b/app/assets/javascripts/leap_web_users/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/users.js b/app/assets/javascripts/users.js new file mode 100644 index 0000000..8486756 --- /dev/null +++ b/app/assets/javascripts/users.js @@ -0,0 +1,132 @@ +(function() { + // + // LOCAL FUNCTIONS + // + + var poll_users, + prevent_default, + form_failed, + form_passed, + clear_errors, + update_user; + + prevent_default = function(event) { + return event.preventDefault(); + }; + + poll_users = function(query, process) { + return $.get("/1/users.json", { + query: query + }).done(process); + }; + + clear_errors = function() { + return $('#messages').empty(); + }; + + update_user = function(submitEvent) { + var form = submitEvent.target; + var token = form.dataset.token; + var url = form.action; + var req = $.ajax({ + url: url, + type: 'PUT', + headers: { Authorization: 'Token token="' + token + '"' }, + data: $(form).serialize() + }); + req.done( function() { + $(form).find('input[type="submit"]').button('reset'); + }); + }; + + markAsSubmitted = function(submitEvent) { + var form = submitEvent.target; + $(form).addClass('submitted') + // bootstrap loading state: + $(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 + // + + srp.session = new srp.Session(); + + srp.signedUp = function() { + return srp.login(); + }; + + srp.loggedIn = function() { + return window.location = '/'; + }; + + srp.updated = function() { + return window.location = '/users/' + srp.session.id(); + }; + + // + // if a json request returns an error, this function gets called and + // decorates the appropriate fields with the error messages. + // + srp.error = function(message) { + clear_errors(); + var errors = extractErrors(message); + displayErrors(errors); + resetButtons(); + } + + function extractErrors(message) { + if ($.isPlainObject(message) && message.errors) { + return message.errors; + } else { + 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); + } + }; + + // + // INIT + // + + $(document).ready(function() { + $('form').submit(markAsSubmitted); + $('#new_user').submit(prevent_default); + $('#new_user').submit(srp.signup); + $('#new_session').submit(prevent_default); + $('#new_session').submit(srp.login); + $('#update_login_and_password').submit(prevent_default); + $('#update_login_and_password').submit(srp.update); + $('#update_pgp_key').submit(prevent_default); + $('#update_pgp_key').submit(update_user); + return $('#user-typeahead').typeahead({ + source: poll_users + }); + }); + +}).call(this); diff --git a/app/assets/stylesheets/leap_web_users/.gitkeep b/app/assets/stylesheets/leap_web_users/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/.gitkeep b/app/controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/account_settings_controller.rb b/app/controllers/account_settings_controller.rb new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/controller_extension/authentication.rb b/app/controllers/controller_extension/authentication.rb new file mode 100644 index 0000000..03d3989 --- /dev/null +++ b/app/controllers/controller_extension/authentication.rb @@ -0,0 +1,75 @@ +module ControllerExtension::Authentication + extend ActiveSupport::Concern + + private + + included do + helper_method :current_user, :logged_in?, :admin? + end + + def current_user + @current_user ||= token_authenticate || warden.user + end + + def logged_in? + !!current_user + end + + def require_login + access_denied unless logged_in? + end + + # some actions only make sense if you are not logged in yet. + # (login, signup). If a user tries to perform these they will + # be redirected to their dashboard. + def redirect_if_logged_in + redirect_to home_url if logged_in? + end + + def access_denied + respond_to do |format| + format.html do + if logged_in? + redirect_to home_url, :alert => t(:not_authorized) + else + redirect_to login_url, :alert => t(:not_authorized_login) + end + end + format.json do + render :json => {'error' => t(:not_authorized)}, status: :unprocessable_entity + end + end + end + + def admin? + current_user && current_user.is_admin? + end + + def require_admin + access_denied unless admin? + end + + def authentication_errors + return unless attempted_login? + errors = get_warden_errors + errors.inject({}) do |translated,err| + translated[err.first] = I18n.t(err.last) + translated + end + end + + def get_warden_errors + if strategy = warden.winning_strategy + message = strategy.message + # in case we get back the default message to fail! + message.respond_to?(:inject) ? message : { base: message } + else + { login: :all_strategies_failed } + end + end + + def attempted_login? + request.env['warden.options'] && + request.env['warden.options'][:attempted_path] + end +end diff --git a/app/controllers/controller_extension/token_authentication.rb b/app/controllers/controller_extension/token_authentication.rb new file mode 100644 index 0000000..6e0a6ce --- /dev/null +++ b/app/controllers/controller_extension/token_authentication.rb @@ -0,0 +1,27 @@ +module ControllerExtension::TokenAuthentication + extend ActiveSupport::Concern + + def token + @token ||= authenticate_with_http_token do |token_id, options| + Token.find(token_id) + end + end + + def token_authenticate + @token_authenticated ||= token.authenticate if token + end + + def require_token + access_denied unless token_authenticate + end + + def logout + super + clear_token + end + + def clear_token + token.destroy if token + end +end + diff --git a/app/controllers/keys_controller.rb b/app/controllers/keys_controller.rb new file mode 100644 index 0000000..fb28901 --- /dev/null +++ b/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/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..8919a4d --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,28 @@ +class SessionsController < ApplicationController + + before_filter :redirect_if_logged_in, :only => [:new] + + def new + @session = Session.new + if authentication_errors + @errors = authentication_errors + render :status => 422 + end + end + + def destroy + logout + redirect_to home_url + end + + # + # 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}" + # throw :warden, response.finish + #end + +end diff --git a/app/controllers/users_base_controller.rb b/app/controllers/users_base_controller.rb new file mode 100644 index 0000000..9becf0d --- /dev/null +++ b/app/controllers/users_base_controller.rb @@ -0,0 +1,18 @@ +# +# common base class for all user related controllers +# + +class UsersBaseController < ApplicationController + + protected + + def fetch_user + @user = User.find(params[:user_id] || params[:id]) + if !@user && admin? + redirect_to users_url, :alert => t(:no_such_thing, :thing => 'user') + elsif !admin? && @user != current_user + access_denied + end + end + +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..c8e09b6 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,69 @@ +# +# This is an HTML-only controller. For the JSON-only controller, see v1/users_controller.rb +# + +class UsersController < UsersBaseController + + before_filter :require_login, :except => [:new] + before_filter :redirect_if_logged_in, :only => [:new] + before_filter :require_admin, :only => [:index, :deactivate, :enable] + before_filter :fetch_user, :only => [:show, :edit, :update, :destroy, :deactivate, :enable] + + respond_to :html + + def index + if params[:query] + if @user = User.find_by_login(params[:query]) + redirect_to @user + return + else + @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ) + end + else + @users = User.by_created_at.descending + end + @users = @users.limit(100) + end + + def new + @user = User.new + end + + def show + end + + 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 + respond_with @user + end + + def enable + @user.enabled = true + @user.save + respond_with @user + end + + def destroy + @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 bye_url + end + end + +end diff --git a/app/controllers/v1/messages_controller.rb b/app/controllers/v1/messages_controller.rb new file mode 100644 index 0000000..f71d0f1 --- /dev/null +++ b/app/controllers/v1/messages_controller.rb @@ -0,0 +1,25 @@ +module V1 + class MessagesController < ApplicationController + + skip_before_filter :verify_authenticity_token + before_filter :require_token + + respond_to :json + + def index + render json: (current_user ? current_user.messages : [] ) + end + + def update + message = Message.find(params[:id]) + if (message and current_user) + message.mark_as_read_by(current_user) + message.save + render json: true + else + render json: false + end + end + + end +end diff --git a/app/controllers/v1/sessions_controller.rb b/app/controllers/v1/sessions_controller.rb new file mode 100644 index 0000000..eae3a1e --- /dev/null +++ b/app/controllers/v1/sessions_controller.rb @@ -0,0 +1,45 @@ +module V1 + class SessionsController < ApplicationController + + skip_before_filter :verify_authenticity_token + before_filter :require_token, only: :destroy + + def new + @session = Session.new + if authentication_errors + @errors = authentication_errors + render :status => 422 + end + end + + def create + logout if logged_in? + if params['A'] + authenticate! + else + @user = User.find_by_login(params['login']) + render :json => {salt: @user.salt} + end + end + + def update + authenticate! + @token = Token.create(:user_id => current_user.id) + session[:token] = @token.id + render :json => login_response + end + + def destroy + logout + head :no_content + end + + protected + + def login_response + handshake = session.delete(:handshake) || {} + handshake.to_hash.merge(:id => current_user.id, :token => @token.id) + end + + end +end diff --git a/app/controllers/v1/users_controller.rb b/app/controllers/v1/users_controller.rb new file mode 100644 index 0000000..8897d01 --- /dev/null +++ b/app/controllers/v1/users_controller.rb @@ -0,0 +1,32 @@ +module V1 + class UsersController < UsersBaseController + + skip_before_filter :verify_authenticity_token + before_filter :fetch_user, :only => [:update] + before_filter :require_admin, :only => [:index] + before_filter :require_token, :only => [:update] + + respond_to :json + + # used for autocomplete for admins in the web ui + def index + if params[:query] + @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ) + respond_with @users.map(&:login).sort + else + render :json => {'error' => 'query required', 'status' => :unprocessable_entity} + end + end + + def create + @user = Account.create(params[:user]) + respond_with @user # return ID instead? + end + + def update + @user.account.update params[:user] + respond_with @user + end + + end +end diff --git a/app/controllers/webfinger_controller.rb b/app/controllers/webfinger_controller.rb new file mode 100644 index 0000000..8872802 --- /dev/null +++ b/app/controllers/webfinger_controller.rb @@ -0,0 +1,19 @@ +class WebfingerController < ApplicationController + + respond_to :xml, :json + layout false + + def host_meta + @host_meta = Webfinger::HostMetaPresenter.new(request) + respond_with @host_meta + end + + def search + username = params[:q].split('@')[0].to_s.downcase + user = User.find_by_login(username) + raise RECORD_NOT_FOUND, 'User not found' unless user.present? + @presenter = Webfinger::UserPresenter.new(user, request) + respond_with @presenter + end + +end diff --git a/app/designs/message/by_user_ids_to_show.js b/app/designs/message/by_user_ids_to_show.js new file mode 100644 index 0000000..e33566b --- /dev/null +++ b/app/designs/message/by_user_ids_to_show.js @@ -0,0 +1,7 @@ +function (doc) { + if (doc.type === 'Message' && doc.user_ids_to_show && Array.isArray(doc.user_ids_to_show)) { + doc.user_ids_to_show.forEach(function (userId) { + emit(userId, 1); + }); + } +} diff --git a/app/designs/message/by_user_ids_to_show_and_created_at.js b/app/designs/message/by_user_ids_to_show_and_created_at.js new file mode 100644 index 0000000..54e4604 --- /dev/null +++ b/app/designs/message/by_user_ids_to_show_and_created_at.js @@ -0,0 +1,9 @@ +// not using at moment +// call with something like Message.by_user_ids_to_show_and_created_at.startkey([user_id, start_date]).endkey([user_id,end_date]) +function (doc) { + if (doc.type === 'Message' && doc.user_ids_to_show && Array.isArray(doc.user_ids_to_show)) { + doc.user_ids_to_show.forEach(function (userId) { + emit([userId, doc.created_at], 1); + }); + } +} diff --git a/app/designs/user/by_created_at_and_one_month_warning_not_sent.js b/app/designs/user/by_created_at_and_one_month_warning_not_sent.js new file mode 100644 index 0000000..53a95de --- /dev/null +++ b/app/designs/user/by_created_at_and_one_month_warning_not_sent.js @@ -0,0 +1,5 @@ +function (doc) { + if ((doc['type'] == 'User') && (doc['created_at'] != null) && (doc['one_month_warning_sent'] == null)) { + emit(doc['created_at'], 1); + } +} diff --git a/app/helpers/.gitkeep b/app/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/helpers/email_aliases_helper.rb b/app/helpers/email_aliases_helper.rb new file mode 100644 index 0000000..b56b068 --- /dev/null +++ b/app/helpers/email_aliases_helper.rb @@ -0,0 +1,11 @@ +module EmailAliasesHelper + + def email_alias_form(options = {}) + simple_form_for [@user, EmailAlias.new()], + :html => {:class => "form-horizontal email-alias form"}, + :validate => true do |f| + yield f + end + end + +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000..309f8b2 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000..f56faab --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,14 @@ +module UsersHelper + + def user_form_class(*classes) + (classes + ['user', 'form', (@user.new_record? ? 'new' : 'edit')]).compact.join(' ') + end + + def wrapped(item, options = {}) + options[:as] ||= :div + content_tag options[:as], :class => dom_class(item), :id => dom_id(item) do + yield + end + end + +end diff --git a/app/models/account.rb b/app/models/account.rb new file mode 100644 index 0000000..cf998e4 --- /dev/null +++ b/app/models/account.rb @@ -0,0 +1,68 @@ +# +# 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 + + attr_reader :user + + def initialize(user = nil) + @user = user + end + + # Returns the user record so it can be used in views. + def self.create(attrs) + @user = User.create(attrs).tap do |user| + Identity.create_for user + end + end + + def update(attrs) + if attrs[:password_verifier].present? + update_login(attrs[:login]) + @user.update_attributes attrs.slice(:password_verifier, :password_salt) + end + # TODO: move into identity controller + 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.disable_all_for(@user) + @user.destroy + end + + protected + + def update_login(login) + return unless login.present? + @old_identity = Identity.for(@user) + @user.login = login + @new_identity = Identity.for(@user) # based on the new login + @old_identity.destination = @user.email_address # alias old -> new + end + + def update_pgp_key(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/app/models/email.rb b/app/models/email.rb new file mode 100644 index 0000000..a9a503f --- /dev/null +++ b/app/models/email.rb @@ -0,0 +1,26 @@ +class Email < String + include ActiveModel::Validations + + validates :email, + :format => { + :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/, #local part of email is case-sensitive, so allow uppercase letter. + :message => "needs to be a valid email address" + } + + def to_partial_path + "emails/email" + end + + def to_param + to_s + end + + def email + self + end + + def handle + self.split('@').first + end + +end diff --git a/app/models/identity.rb b/app/models/identity.rb new file mode 100644 index 0000000..9b97b51 --- /dev/null +++ b/app/models/identity.rb @@ -0,0 +1,136 @@ +class Identity < CouchRest::Model::Base + include LoginFormatValidation + + use_database :identities + + belongs_to :user + + property :address, LocalEmail + property :destination, Email + property :keys, HashWithIndifferentAccess + + validate :unique_forward + validate :alias_available + validate :address_local_email + validate :destination_email + + design do + view :by_user_id + view :by_address_and_destination + view :by_address + view :pgp_key_by_email, + map: <<-EOJS + function(doc) { + if (doc.type != 'Identity') { + return; + } + if (typeof doc.keys === "object") { + 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 + + def self.for(user, attributes = {}) + find_for(user, attributes) || build_for(user, attributes) + end + + def self.find_for(user, attributes = {}) + attributes.reverse_merge! attributes_from_user(user) + find_by_address_and_destination [attributes[:address], attributes[:destination]] + end + + def self.build_for(user, attributes = {}) + attributes.reverse_merge! attributes_from_user(user) + Identity.new(attributes) + end + + def self.create_for(user, attributes = {}) + identity = build_for(user, attributes) + identity.save + 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, + destination: user.email_address + } + 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, key) + return if keys[type] == key.to_s + write_attribute('keys', keys.merge(type => key.to_s)) + end + + # for LoginFormatValidation + def login + self.address.handle + end + + protected + + def unique_forward + same = Identity.find_by_address_and_destination([address, destination]) + if same && same != self + errors.add :base, "This alias already exists" + end + end + + def alias_available + same = Identity.find_by_address(address) + if same && same.user != self.user + errors.add :base, "This email has already been taken" + end + end + + def address_local_email + return if address.valid? #this ensures it is LocalEmail + self.errors.add(:address, address.errors.messages[:email].first) #assumes only one error + end + + def destination_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 + +end diff --git a/app/models/local_email.rb b/app/models/local_email.rb new file mode 100644 index 0000000..2b4c65e --- /dev/null +++ b/app/models/local_email.rb @@ -0,0 +1,68 @@ +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] + end + + validates :email, + :format => { + :with => /@#{domain}\Z/i, + :message => "needs to end in @#{domain}" + } + + validate :handle_allowed + + def initialize(s) + super + append_domain_if_needed + end + + def to_key + [handle] + end + + def domain + LocalEmail.domain + end + + protected + + def append_domain_if_needed + unless self.index('@') + self << '@' + domain + 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/app/models/login_format_validation.rb b/app/models/login_format_validation.rb new file mode 100644 index 0000000..c1fcf70 --- /dev/null +++ b/app/models/login_format_validation.rb @@ -0,0 +1,21 @@ +module LoginFormatValidation + extend ActiveSupport::Concern + + #TODO: Probably will replace this. Playing with using it for aliases too, but won't want it connected to login field. + + included do + # Have multiple regular expression validations so we can get specific error messages: + validates :login, + :format => { :with => /\A.{2,}\z/, + :message => "Must have at least two characters"} + validates :login, + :format => { :with => /\A[a-z\d_\.-]+\z/, + :message => "Only lowercase letters, digits, . - and _ allowed."} + validates :login, + :format => { :with => /\A[a-z].*\z/, + :message => "Must begin with a lowercase letter"} + validates :login, + :format => { :with => /\A.*[a-z\d]\z/, + :message => "Must end with a letter or digit"} + end +end diff --git a/app/models/message.rb b/app/models/message.rb new file mode 100644 index 0000000..424f094 --- /dev/null +++ b/app/models/message.rb @@ -0,0 +1,29 @@ +class Message < CouchRest::Model::Base + + use_database :messages + + property :text, String + property :user_ids_to_show, [String] + property :user_ids_have_shown, [String] # is this necessary to store? + + timestamps! + + design do + own_path = Pathname.new(File.dirname(__FILE__)) + load_views(own_path.join('..', 'designs', 'message')) + end + + def mark_as_read_by(user) + user_ids_to_show.delete(user.id) + # is it necessary to keep track of what users have already seen it? + user_ids_have_shown << user.id unless read_by?(user) + end + + def read_by?(user) + user_ids_have_shown.include?(user.id) + end + + def unread_by?(user) + user_ids_to_show.include?(user.id) + end +end diff --git a/app/models/pgp_key.rb b/app/models/pgp_key.rb new file mode 100644 index 0000000..66f8660 --- /dev/null +++ b/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/app/models/service_level.rb b/app/models/service_level.rb new file mode 100644 index 0000000..299aaf1 --- /dev/null +++ b/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/app/models/session.rb b/app/models/session.rb new file mode 100644 index 0000000..0d7e10e --- /dev/null +++ b/app/models/session.rb @@ -0,0 +1,32 @@ +class Session < SRP::Session + include ActiveModel::Validations + include LoginFormatValidation + + attr_accessor :login + + validates :login, :presence => true + + def initialize(user = nil, aa = nil) + super(user, aa) if user + end + + def persisted? + false + end + + def new_record? + true + end + + def to_model + self + end + + def to_key + [object_id] + end + + def to_param + nil + end +end diff --git a/app/models/token.rb b/app/models/token.rb new file mode 100644 index 0000000..4856c31 --- /dev/null +++ b/app/models/token.rb @@ -0,0 +1,69 @@ +class Token < CouchRest::Model::Base + + use_database :tokens + + belongs_to :user + + # timestamps! does not create setters and only sets updated_at + # if the object has changed and been saved. Instead of triggering + # that we rather use our own property we have control over: + property :last_seen_at, Time, accessible: false + + 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 + return nil + else + touch + return user + end + end + + # Tokens can be cleaned up in different ways. + # So let's make sure we don't crash if they disappeared + def destroy_with_rescue + destroy_without_rescue + rescue RestClient::ResourceNotFound + end + alias_method_chain :destroy, :rescue + + def touch + self.last_seen_at = Time.now + save + end + + def expired? + Token.expires_after and + last_seen_at < Token.expires_after.minutes.ago + end + + def initialize(*args) + super + if new_record? + self.id = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '') + self.last_seen_at = Time.now + end + end +end + diff --git a/app/models/unauthenticated_user.rb b/app/models/unauthenticated_user.rb new file mode 100644 index 0000000..0fc17d2 --- /dev/null +++ b/app/models/unauthenticated_user.rb @@ -0,0 +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/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..c297ac8 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,179 @@ +class User < CouchRest::Model::Base + include LoginFormatValidation + + use_database :users + + property :login, String, :accessible => true + property :password_verifier, String, :accessible => true + property :password_salt, String, :accessible => true + + 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 + + property :one_month_warning_sent, TrueClass + + before_save :update_effective_service_level + + validates :login, :password_salt, :password_verifier, + :presence => true + + validates :login, + :uniqueness => true, + :if => :serverside? + + validate :login_is_unique_alias + + validates :password_salt, :password_verifier, + :format => { :with => /\A[\dA-Fa-f]+\z/, :message => "Only hex numbers allowed" } + + validates :password, :presence => true, + :confirmation => true, + :format => { :with => /.{8}.*/, :message => "needs to be at least 8 characters long" } + + timestamps! + + design do + own_path = Pathname.new(File.dirname(__FILE__)) + load_views(own_path.join('..', 'designs', 'user')) + view :by_login + view :by_created_at + end # end of design + + def to_json(options={}) + { + :login => login, + :ok => valid? + }.to_json(options) + end + + def salt + password_salt.hex + end + + def verifier + password_verifier.hex + end + + def username + login + end + + def email_address + LocalEmail.new(login) + end + + # Since we are storing admins by login, we cannot allow admins to change their login. + def is_admin? + APP_CONFIG['admins'].include? self.login + end + + def most_recent_tickets(count=3) + Ticket.for_user(self).limit(count).all #defaults to having most recent updated first + end + + def messages(unseen = true) + #TODO for now this only shows unseen messages. Will we ever want seen ones? Is it necessary to store? + + # we don't want to emit all the userids associated with a message, so only emit id and text. + Message.by_user_ids_to_show.key(self.id).map { |message| [message.id, message.text] } + + end + + # DEPRECATED + # + # Please set the key on the identity directly + # WARNING: This will not be serialized with the user record! + # It is only a workaround for the key form. + def public_key=(value) + identity.set_key(:pgp, value) + end + + # DEPRECATED + # + # Please access identity.keys[:pgp] directly + def public_key + identity.keys[:pgp] + end + + def account + Account.new(self) + end + + def identity + @identity ||= Identity.for(self) + end + + def refresh_identity + @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 + + + def self.send_one_month_warnings + + # To determine warnings to send, need to get all users where one_month_warning_sent is not set, and where it was created greater than or equal to 1 month ago. + # TODO: might want to further limit to enabled accounts, and, based on provider's service level configuration, for particular service levels. + users_to_warn = User.by_created_at_and_one_month_warning_not_sent.endkey(Time.now-1.month) + + users_to_warn.each do |user| + # instead of loop could use something like: + # message.user_ids_to_show = users_to_warn.map(&:id) + # but would still need to loop through users to store one_month_warning_sent + + if !@message + # create a message for today's date + # only want to create once, and only if it will be used. + @message = Message.new(:text => I18n.t(:payment_one_month_warning, :date_in_one_month => (Time.now+1.month).strftime("%Y-%d-%m"))) + end + + @message.user_ids_to_show << user.id + user.one_month_warning_sent = true + user.save + end + @message.save if @message + + end + + protected + + ## + # Validation Functions + ## + + def login_is_unique_alias + alias_identity = Identity.find_by_address(self.email_address) + return if alias_identity.blank? + if alias_identity.user != self + errors.add(:login, "has already been taken") + end + end + + def password + password_verifier + end + + # used as a condition for validations that are server side only + 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/app/views/.gitkeep b/app/views/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/views/emails/_email.html.haml b/app/views/emails/_email.html.haml new file mode 100644 index 0000000..ea59cec --- /dev/null +++ b/app/views/emails/_email.html.haml @@ -0,0 +1,6 @@ += wrapped(email, local_assigns) do + = email + - if local_assigns[:with].try(:include?, :delete) + = link_to(user_email_alias_path(@user, email), :method => :delete) do + %i.icon-remove + diff --git a/app/views/sessions/new.html.haml b/app/views/sessions/new.html.haml new file mode 100644 index 0000000..771dc97 --- /dev/null +++ b/app/views/sessions/new.html.haml @@ -0,0 +1,10 @@ +.span1 +.span9 + = render :partial => 'users/warnings' + %h2=t :login + = simple_form_for [:api, @session], :validate => true, :html => { :id => :new_session, :class => 'form-horizontal' } do |f| + = f.input :login, :required => false, :label => t(:username), :input_html => { :id => :srp_username } + = f.input :password, :required => false, :input_html => { :id => :srp_password } + .form-actions + = f.button :submit, :value => t(:login), :class => 'btn-primary' + = link_to t(:cancel), home_path, :class => 'btn' diff --git a/app/views/sessions/new.json.erb b/app/views/sessions/new.json.erb new file mode 100644 index 0000000..36154b8 --- /dev/null +++ b/app/views/sessions/new.json.erb @@ -0,0 +1,3 @@ +{ +"errors": <%= raw @errors.to_json %> +} diff --git a/app/views/users/_change_password.html.haml b/app/views/users/_change_password.html.haml new file mode 100644 index 0000000..425e3ee --- /dev/null +++ b/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/app/views/users/_change_pgp_key.html.haml b/app/views/users/_change_pgp_key.html.haml new file mode 100644 index 0000000..e465125 --- /dev/null +++ b/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/app/views/users/_change_service_level.html.haml b/app/views/users/_change_service_level.html.haml new file mode 100644 index 0000000..61e67d9 --- /dev/null +++ b/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/app/views/users/_destroy_account.html.haml b/app/views/users/_destroy_account.html.haml new file mode 100644 index 0000000..445f3c4 --- /dev/null +++ b/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/app/views/users/_edit.html.haml b/app/views/users/_edit.html.haml new file mode 100644 index 0000000..1d2b68a --- /dev/null +++ b/app/views/users/_edit.html.haml @@ -0,0 +1,14 @@ +-# +-# edit user form, used by both show and edit actions. +-# +-# 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/app/views/users/_user.html.haml b/app/views/users/_user.html.haml new file mode 100644 index 0000000..583d22f --- /dev/null +++ b/app/views/users/_user.html.haml @@ -0,0 +1,4 @@ +%tr + %td= link_to user.login, user + %td= l(user.created_at, :format => :short) + %td= l(user.updated_at, :format => :short) diff --git a/app/views/users/_warnings.html.haml b/app/views/users/_warnings.html.haml new file mode 100644 index 0000000..79ab103 --- /dev/null +++ b/app/views/users/_warnings.html.haml @@ -0,0 +1,12 @@ +%noscript + %div.alert.alert-error=t :js_required_html +#cookie_warning.alert.alert-error{:style => "display:none"} + =t :cookie_disabled_warning +:javascript + document.cookie = "testing=cookies_enabled; path=/"; + if(document.cookie.indexOf("testing=cookies_enabled") < 0) + { + document.getElementById('cookie_warning').style.display = 'block'; + } else { + document.getElementById('cookie_warning').style.display = 'none'; + } \ No newline at end of file diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml new file mode 100644 index 0000000..434c025 --- /dev/null +++ b/app/views/users/edit.html.haml @@ -0,0 +1 @@ += render 'edit' diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml new file mode 100644 index 0000000..fc1001e --- /dev/null +++ b/app/views/users/index.html.haml @@ -0,0 +1,13 @@ +- @show_navigation = false + += form_tag users_path, :method => :get, :class => "form-search" do + .input-append + = text_field_tag :query, params[:query], :id => 'user-typeahead', :class => "search-query", :autocomplete => :off + %button.btn{:type => :submit}= t(:search) + +%table.table.table-striped + %tr + %th= t(:username) + %th= t(:created) + %th= t(:updated) + = render @users.all diff --git a/app/views/users/new.html.haml b/app/views/users/new.html.haml new file mode 100644 index 0000000..aecf831 --- /dev/null +++ b/app/views/users/new.html.haml @@ -0,0 +1,19 @@ +-# +-# This form is handled entirely by javascript, so take care when changing element ids. +-# + +- form_options = {:url => '/not-used', :html => {:id => 'new_user', :class => user_form_class('form-horizontal')}, :validate => true} + +.span1 +.span9 + = render :partial => 'warnings' + %h2=t :signup + = simple_form_for(@user, form_options) do |f| + %legend= t(:signup_message) + = 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, :validate => true, :input_html => { :id => :srp_password_confirmation } + .form-actions + = f.button :submit, :value => t(:signup), :class => 'btn btn-primary' + = link_to t(:cancel), home_path, :class => 'btn' + diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml new file mode 100644 index 0000000..c587017 --- /dev/null +++ b/app/views/users/show.html.haml @@ -0,0 +1,25 @@ +.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[:billing] + .container-fluid + .row-fluid + = home_page_buttons(true) \ No newline at end of file diff --git a/app/views/v1/sessions/new.json.erb b/app/views/v1/sessions/new.json.erb new file mode 100644 index 0000000..36154b8 --- /dev/null +++ b/app/views/v1/sessions/new.json.erb @@ -0,0 +1,3 @@ +{ +"errors": <%= raw @errors.to_json %> +} diff --git a/app/views/webfinger/host_meta.xml.erb b/app/views/webfinger/host_meta.xml.erb new file mode 100644 index 0000000..cfcbcc0 --- /dev/null +++ b/app/views/webfinger/host_meta.xml.erb @@ -0,0 +1,11 @@ + + + + <%= @host_meta.subject %> + + <%- @host_meta.links.each do |rel, link| %> + + <%- end %> + diff --git a/app/views/webfinger/search.xml.erb b/app/views/webfinger/search.xml.erb new file mode 100644 index 0000000..7328552 --- /dev/null +++ b/app/views/webfinger/search.xml.erb @@ -0,0 +1,7 @@ + + + <%= @presenter.subject %> + <%- @presenter.links.each do |rel, link| %> + type=<%=link[:type]%> href="<%= link[:key] %>"/> + <% end %> + diff --git a/test/factories.rb b/test/factories.rb index 6c671f8..98bb39b 100644 --- a/test/factories.rb +++ b/test/factories.rb @@ -1,3 +1,37 @@ Dir.glob(Rails.root.join('**','test','factories.rb')) do |factory_file| require factory_file end +FactoryGirl.define do + + factory :user do + login { Faker::Internet.user_name } + password_verifier "1234ABCD" + password_salt "4321AB" + + factory :user_with_settings do + email_forward { Faker::Internet.email } + email_aliases_attributes do + {:a => Faker::Internet.user_name + '@' + APP_CONFIG[:domain]} + end + end + + factory :admin_user do + after(:build) do |admin| + admin.stubs(:is_admin?).returns(true) + end + end + end + + factory :token do + user + end + + factory :pgp_key do + keyblock <<-EOPGP +-----BEGIN PGP PUBLIC KEY BLOCK----- ++Dummy+PGP+KEY+++Dummy+PGP+KEY+++Dummy+PGP+KEY+++Dummy+PGP+KEY+ +#{SecureRandom.base64(4032)} +-----END PGP PUBLIC KEY BLOCK----- + EOPGP + end +end diff --git a/test/functional/application_controller_test.rb b/test/functional/application_controller_test.rb new file mode 100644 index 0000000..c4c922b --- /dev/null +++ b/test/functional/application_controller_test.rb @@ -0,0 +1,28 @@ +require 'test_helper' + +class ApplicationControllerTest < ActionController::TestCase + + def setup + # so we can test the effect on the response + @controller.response = @response + end + + def test_require_login_redirect + @controller.send(:require_login) + assert_access_denied(true, false) + end + + def test_require_login + login + @controller.send(:require_login) + assert_access_denied(false) + end + + def test_require_admin + login + @current_user.expects(:is_admin?).returns(false) + @controller.send(:require_admin) + assert_access_denied + end + +end diff --git a/test/functional/helper_methods_test.rb b/test/functional/helper_methods_test.rb new file mode 100644 index 0000000..44226ae --- /dev/null +++ b/test/functional/helper_methods_test.rb @@ -0,0 +1,39 @@ +# +# Testing and documenting the helper methods available from +# ApplicationController +# + +require 'test_helper' + +class HelperMethodsTest < ActionController::TestCase + tests ApplicationController + + # we test them right in here... + include ApplicationController._helpers + + # the helpers all reference the controller. + def controller + @controller + end + + def test_current_user + login + assert_equal @current_user, current_user + end + + def test_logged_in + login + assert logged_in? + end + + def test_logged_out + assert !logged_in? + end + + def test_admin + login + @current_user.expects(:is_admin?).returns(bool = stub) + assert_equal bool, admin? + end + +end diff --git a/test/functional/keys_controller_test.rb b/test/functional/keys_controller_test.rb new file mode 100644 index 0000000..863be93 --- /dev/null +++ b/test/functional/keys_controller_test.rb @@ -0,0 +1,32 @@ +require 'test_helper' + +class KeysControllerTest < ActionController::TestCase + + test "get existing public key" do + public_key = 'my public key' + @user = stub_record :user, :public_key => public_key + User.stubs(:find_by_login).with(@user.login).returns(@user) + get :show, :login => @user.login + assert_response :success + assert_equal "text/text", response.content_type + assert_equal public_key, response.body + end + + test "get non-existing public key for user" do + # this isn't a scenerio that should generally occur. + @user = stub_record :user + User.stubs(:find_by_login).with(@user.login).returns(@user) + get :show, :login => @user.login + assert_response :success + assert_equal "text/text", response.content_type + assert_equal '', response.body.strip + end + + test "get public key for non-existing user" do + # raise 404 error if user doesn't exist (doesn't need to be this routing error, but seems fine to assume for now): + assert_raise(ActionController::RoutingError) { + get :show, :login => 'asdkljslksjfdlskfj' + } + end + +end diff --git a/test/functional/sessions_controller_test.rb b/test/functional/sessions_controller_test.rb new file mode 100644 index 0000000..fe7903f --- /dev/null +++ b/test/functional/sessions_controller_test.rb @@ -0,0 +1,59 @@ +require 'test_helper' + +# This is a simple controller unit test. +# We're stubbing out both warden and srp. +# There's an integration test testing the full rack stack and srp +class SessionsControllerTest < ActionController::TestCase + + setup do + @user = stub :login => "me", :id => 123 + @client_hex = 'a123' + end + + test "should get login screen" do + get :new + assert_response :success + assert_equal "text/html", response.content_type + assert_template "sessions/new" + end + + test "redirect to home_url if logged in" do + login + get :new + assert_response :redirect + assert_redirected_to home_url + end + + test "renders json" do + get :new, :format => :json + assert_response :success + assert_json_error nil + end + + test "renders warden errors" do + request.env['warden.options'] = {attempted_path: '/1/sessions/asdf.json'} + strategy = stub :message => {:field => :translate_me} + request.env['warden'].stubs(:winning_strategy).returns(strategy) + I18n.expects(:t).with(:translate_me).at_least_once.returns("translation stub") + get :new, :format => :json + assert_response 422 + assert_json_error :field => "translation stub" + end + + test "renders failed attempt message" do + request.env['warden.options'] = {attempted_path: '/1/sessions/asdf.json'} + request.env['warden'].stubs(:winning_strategy).returns(nil) + get :new, :format => :json + assert_response 422 + assert_json_error :login => I18n.t(:all_strategies_failed) + end + + test "destory should logout" do + login + expect_logout + delete :destroy + assert_response :redirect + assert_redirected_to home_url + end + +end diff --git a/test/functional/test_helpers_test.rb b/test/functional/test_helpers_test.rb new file mode 100644 index 0000000..845e516 --- /dev/null +++ b/test/functional/test_helpers_test.rb @@ -0,0 +1,38 @@ +# +# There are a few test helpers for dealing with login etc. +# We test them here and also document their behaviour. +# + +require 'test_helper' + +class TestHelpersTest < ActionController::TestCase + tests ApplicationController # testing no controller in particular + + def test_login_stubs_warden + login + assert_equal @current_user, request.env['warden'].user + end + + def test_login_token_authenticates + login + assert_equal @current_user, @controller.send(:token_authenticate) + end + + def test_login_stubs_token + login + assert @token + assert_equal @current_user, @token.authenticate + end + + def test_login_adds_token_header + login + token_present = @controller.authenticate_with_http_token do |token, options| + assert_equal @token.id, token + end + # authenticate_with_http_token just returns nil and does not + # execute the block if there is no token. So we have to also + # ensure it was run: + assert token_present + end +end + diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb new file mode 100644 index 0000000..0713836 --- /dev/null +++ b/test/functional/users_controller_test.rb @@ -0,0 +1,165 @@ +require 'test_helper' + +class UsersControllerTest < ActionController::TestCase + + test "should get new" do + get :new + assert_equal User, assigns(:user).class + assert_response :success + end + + test "new should redirect logged in users" do + login + get :new + assert_response :redirect + assert_redirected_to home_path + end + + test "failed show without login" do + user = find_record :user + get :show, :id => user.id + assert_response :redirect + assert_redirected_to login_path + end + + test "user can see user" do + user = find_record :user, + :most_recent_tickets => [] + login user + get :show, :id => user.id + assert_response :success + end + + test "admin can see other user" do + user = find_record :user, + :most_recent_tickets => [] + login :is_admin? => true + get :show, :id => user.id + assert_response :success + + end + + test "user cannot see other user" do + user = find_record :user, + :most_recent_tickets => [] + login + get :show, :id => user.id + assert_response :redirect + assert_access_denied + end + + test "may not show non-existing user without auth" do + nonid = 'thisisnotanexistinguserid' + + get :show, :id => nonid + assert_access_denied(true, false) + end + + test "may not show non-existing user without admin" do + nonid = 'thisisnotanexistinguserid' + login + + get :show, :id => nonid + assert_access_denied + end + + test "redirect admin to user list for non-existing user" do + nonid = 'thisisnotanexistinguserid' + login :is_admin? => true + get :show, :id => nonid + assert_response :redirect + assert_equal({:alert => "No such user."}, flash.to_hash) + assert_redirected_to users_path + end + + test "should get edit view" do + user = find_record :user + + login user + get :edit, :id => user.id + + assert_equal user, assigns[:user] + end + + test "admin can destroy user" do + user = find_record :user + + # we destroy the user record and the associated data... + user.expects(:destroy) + Identity.expects(:disable_all_for).with(user) + Ticket.expects(:destroy_all_from).with(user) + + login :is_admin? => true + delete :destroy, :id => user.id + + assert_response :redirect + assert_redirected_to users_path + end + + test "user can cancel account" do + user = find_record :user + + # we destroy the user record and the associated data... + user.expects(:destroy) + Identity.expects(:disable_all_for).with(user) + Ticket.expects(:destroy_all_from).with(user) + + login user + expect_logout + delete :destroy, :id => @current_user.id + + assert_response :redirect + assert_redirected_to bye_url + end + + test "non-admin can't destroy user" do + user = find_record :user + + login + delete :destroy, :id => user.id + + assert_access_denied + end + + test "admin can list users" do + login :is_admin? => true + get :index + + assert_response :success + assert assigns(:users) + end + + test "non-admin can't list users" do + login + get :index + + assert_access_denied + end + + test "admin can search users" do + login :is_admin? => true + get :index, :query => "a" + + assert_response :success + assert assigns(:users) + end + + test "user cannot enable own account" do + user = find_record :user + login + post :enable, :id => user.id + assert_access_denied + end + + test "admin can deactivate user" do + user = find_record :user + assert user.enabled? + user.expects(:save).returns(true) + + login :is_admin? => true + + post :deactivate, :id => user.id + assert !assigns(:user).enabled? + end + +end diff --git a/test/functional/v1/messages_controller_test.rb b/test/functional/v1/messages_controller_test.rb new file mode 100644 index 0000000..24a5b1f --- /dev/null +++ b/test/functional/v1/messages_controller_test.rb @@ -0,0 +1,57 @@ +require 'test_helper' + +class V1::MessagesControllerTest < ActionController::TestCase + + setup do + @user = FactoryGirl.build(:user) + @user.save + @message = Message.new(:text => 'a test message') + @message.user_ids_to_show << @user.id + @message.save + end + + teardown do + @message.destroy + @user.destroy + end + + test "get messages for user" do + login @user + get :index + assert response.body.include? @message.text + assert response.body.include? @message.id + end + + test "mark message read for user" do + login @user + assert @message.user_ids_to_show.include?(@user.id) + assert !@message.user_ids_have_shown.include?(@user.id) + put :update, :id => @message.id + @message.reload + assert !@message.user_ids_to_show.include?(@user.id) + assert @message.user_ids_have_shown.include?(@user.id) + assert_json_response true + end + + test "do not get seen messages" do + login @user + put :update, :id => @message.id + @message.reload + get :index + assert !(response.body.include? @message.text) + assert !(response.body.include? @message.id) + end + + + test "mark read responds even with bad inputs" do + login @user + put :update, :id => 'more nonsense' + assert_json_response false + end + + test "fails if not authenticated" do + get :index, :format => :json + assert_access_denied + end + +end diff --git a/test/functional/v1/sessions_controller_test.rb b/test/functional/v1/sessions_controller_test.rb new file mode 100644 index 0000000..df0d681 --- /dev/null +++ b/test/functional/v1/sessions_controller_test.rb @@ -0,0 +1,62 @@ +require 'test_helper' + +# This is a simple controller unit test. +# We're stubbing out both warden and srp. +# There's an integration test testing the full rack stack and srp +class V1::SessionsControllerTest < ActionController::TestCase + + setup do + @request.env['HTTP_HOST'] = 'api.lvh.me' + @user = stub_record :user, {}, true + @client_hex = 'a123' + end + + test "renders json" do + get :new, :format => :json + assert_response :success + assert_json_error nil + end + + test "renders warden errors" do + request.env['warden.options'] = {attempted_path: 'path/to/controller'} + strategy = stub :message => {:field => :translate_me} + request.env['warden'].stubs(:winning_strategy).returns(strategy) + I18n.expects(:t).with(:translate_me).at_least_once.returns("translation stub") + get :new, :format => :json + assert_response 422 + assert_json_error :field => "translation stub" + end + + # Warden takes care of parsing the params and + # rendering the response. So not much to test here. + test "should perform handshake" do + request.env['warden'].expects(:authenticate!) + # make sure we don't get a template missing error: + @controller.stubs(:render) + post :create, :login => @user.login, 'A' => @client_hex + end + + test "should authenticate" do + request.env['warden'].expects(:authenticate!) + @controller.stubs(:current_user).returns(@user) + handshake = stub(:to_hash => {h: "ash"}) + session[:handshake] = handshake + + post :update, :id => @user.login, :client_auth => @client_hex + + assert_nil session[:handshake] + assert_response :success + assert json_response.keys.include?("id") + assert json_response.keys.include?("token") + assert token = Token.find(json_response['token']) + assert_equal @user.id, token.user_id + end + + test "destroy should logout" do + login + expect_logout + delete :destroy + assert_response 204 + end + +end diff --git a/test/functional/v1/users_controller_test.rb b/test/functional/v1/users_controller_test.rb new file mode 100644 index 0000000..7cd9b0c --- /dev/null +++ b/test/functional/v1/users_controller_test.rb @@ -0,0 +1,74 @@ +require 'test_helper' + +class V1::UsersControllerTest < ActionController::TestCase + + test "user can change settings" do + user = find_record :user + changed_attribs = record_attributes_for :user_with_settings + account_settings = stub + account_settings.expects(:update).with(changed_attribs) + Account.expects(:new).with(user).returns(account_settings) + + login user + put :update, :user => changed_attribs, :id => user.id, :format => :json + + assert_equal user, assigns[:user] + assert_response 204 + assert_equal " ", @response.body + end + + test "admin can update user" do + user = find_record :user + changed_attribs = record_attributes_for :user_with_settings + account_settings = stub + account_settings.expects(:update).with(changed_attribs) + Account.expects(:new).with(user).returns(account_settings) + + login :is_admin? => true + put :update, :user => changed_attribs, :id => user.id, :format => :json + + assert_equal user, assigns[:user] + assert_response 204 + end + + test "user cannot update other user" do + user = find_record :user + login + put :update, :user => record_attributes_for(:user_with_settings), :id => user.id, :format => :json + assert_access_denied + end + + test "should create new user" do + user_attribs = record_attributes_for :user + user = User.new(user_attribs) + Account.expects(:create).with(user_attribs).returns(user) + + post :create, :user => user_attribs, :format => :json + + assert_nil session[:user_id] + assert_json_response user + assert_response :success + end + + test "should redirect to signup form on failed attempt" do + user_attribs = record_attributes_for :user + user_attribs.slice!('login') + user = User.new(user_attribs) + assert !user.valid? + Account.expects(:create).with(user_attribs).returns(user) + + post :create, :user => user_attribs, :format => :json + + assert_json_error user.errors.messages + assert_response 422 + end + + test "admin can autocomplete users" do + login :is_admin? => true + get :index, :query => 'a', :format => :json + + assert_response :success + assert assigns(:users) + end + +end diff --git a/test/functional/webfinger_controller_test.rb b/test/functional/webfinger_controller_test.rb new file mode 100644 index 0000000..6597b69 --- /dev/null +++ b/test/functional/webfinger_controller_test.rb @@ -0,0 +1,33 @@ +require 'test_helper' + +class WebfingerControllerTest < ActionController::TestCase + + test "get host meta xml" do + get :host_meta, :format => :xml + assert_response :success + assert_equal "application/xml", response.content_type + end + + test "get host meta json" do + get :host_meta, :format => :json + assert_response :success + assert_equal "application/json", response.content_type + end + + test "get user webfinger xml" do + @user = stub_record :user, :public_key => 'my public key' + User.stubs(:find_by_login).with(@user.login).returns(@user) + get :search, :q => @user.email_address.to_s, :format => :xml + assert_response :success + assert_equal "application/xml", response.content_type + end + + test "get user webfinger json" do + @user = stub_record :user, :public_key => 'my public key' + User.stubs(:find_by_login).with(@user.login).returns(@user) + get :search, :q => @user.email_address.to_s, :format => :json + assert_response :success + assert_equal "application/json", response.content_type + end + +end diff --git a/test/integration/api/Readme.md b/test/integration/api/Readme.md new file mode 100644 index 0000000..04363bd --- /dev/null +++ b/test/integration/api/Readme.md @@ -0,0 +1,23 @@ +API tests +========== + + +Testing the restful api from a simple python client as that's what we'll be using. + +This test so far mostly demoes the API. We have no SRP calc in there. + +TODO: keep track of the cookies during login. The server uses the session to keep track of the random numbers A and B. + +The output of signup_and_login_wrong_password pretty well describes the SRP API: + +``` +POST: http://localhost:9292/users.json + {"user[password_salt]": "54321", "user[password_verifier]": "12345", "user[login]": "SWQ055"} + -> {"password_salt":"54321","login":"SWQ055"} +POST: http://localhost:9292/sessions + {"A": "12345", "login": "SWQ055"} + -> {"B":"1778367531e93a4c7713c76f67649f35a4211ebc520926ae8c3848cd66171651"} +PUT: http://localhost:9292/sessions/SWQ055 + {"M": "123ABC"} + -> {"errors":[{"login":"Not a valid username/password combination"},{"password":"Not a valid username/password combination"}]} +``` diff --git a/test/integration/api/login_test.rb b/test/integration/api/login_test.rb new file mode 100644 index 0000000..92d153f --- /dev/null +++ b/test/integration/api/login_test.rb @@ -0,0 +1,50 @@ +require 'test_helper' +require_relative 'srp_test' + +class LoginTest < SrpTest + + setup do + register_user + end + + test "requires handshake before validation" do + validate("bla") + assert_json_error login: I18n.t(:all_strategies_failed) + end + + test "login with srp" do + authenticate + assert_equal ["M2", "id", "token"], server_auth.keys + assert last_response.successful? + assert_nil server_auth["errors"] + assert server_auth["M2"] + end + + test "wrong password login attempt" do + authenticate password: "wrong password" + assert_json_error "base" => "Not a valid username/password combination" + assert !last_response.successful? + assert_nil server_auth["M2"] + end + + test "wrong username login attempt" do + assert_raises RECORD_NOT_FOUND do + authenticate login: "wrong login" + end + assert_json_error "base" => "Not a valid username/password combination" + assert !last_response.successful? + assert_nil server_auth + end + + test "logout" do + authenticate + logout + assert_equal 204, last_response.status + end + + test "logout requires token" do + authenticate + logout(nil, {}) + assert_equal 422, last_response.status + end +end diff --git a/test/integration/api/pgp_key_test.rb b/test/integration/api/pgp_key_test.rb new file mode 100644 index 0000000..4c7fb4c --- /dev/null +++ b/test/integration/api/pgp_key_test.rb @@ -0,0 +1,35 @@ +require 'test_helper' +require_relative 'srp_test' + +class PgpKeyTest < SrpTest + + setup do + # todo: prepare user and login without doing the srp dance + register_user + authenticate + end + + test "upload pgp key" do + update_user public_key: key + assert_equal key, Identity.for(@user).keys[:pgp] + end + + # eventually probably want to remove most of this into a non-integration + # functional test + test "prevent uploading invalid key" do + update_user public_key: "invalid key" + assert_nil Identity.for(@user).keys[:pgp] + end + + test "prevent emptying public key" do + update_user public_key: key + update_user public_key: "" + assert_equal key, Identity.for(@user).keys[:pgp] + end + + protected + + def key + @key ||= FactoryGirl.build :pgp_key + end +end diff --git a/test/integration/api/python/flow_with_srp.py b/test/integration/api/python/flow_with_srp.py new file mode 100755 index 0000000..9fc168b --- /dev/null +++ b/test/integration/api/python/flow_with_srp.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +# under development + +import requests +import json +import string +import random +import srp._pysrp as srp +import binascii + +safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x) + +# using globals for now +# server = 'https://dev.bitmask.net/1' +server = 'http://api.lvh.me:3000/1' + +def run_tests(): + login = 'test_' + id_generator() + password = id_generator() + id_generator() + usr = srp.User( login, password, srp.SHA256, srp.NG_1024 ) + print_and_parse(signup(login, password)) + + auth = print_and_parse(authenticate(usr)) + verify_or_debug(auth, usr) + assert usr.authenticated() + + usr = change_password(auth['id'], login, auth['token']) + + auth = print_and_parse(authenticate(usr)) + verify_or_debug(auth, usr) + # At this point the authentication process is complete. + assert usr.authenticated() + +# let's have some random name +def id_generator(size=6, chars=string.ascii_lowercase + string.digits): + return ''.join(random.choice(chars) for x in range(size)) + +# log the server communication +def print_and_parse(response): + request = response.request + print request.method + ': ' + response.url + if hasattr(request, 'data'): + print " " + json.dumps(response.request.data) + print " -> " + response.text + try: + return json.loads(response.text) + except ValueError: + return None + +def signup(login, password): + salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 ) + user_params = { + 'user[login]': login, + 'user[password_verifier]': binascii.hexlify(vkey), + 'user[password_salt]': binascii.hexlify(salt) + } + return requests.post(server + '/users.json', data = user_params, verify = False) + +def change_password(user_id, login, token): + password = id_generator() + id_generator() + salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 ) + user_params = { + 'user[password_verifier]': binascii.hexlify(vkey), + 'user[password_salt]': binascii.hexlify(salt) + } + auth_headers = { 'Authorization': 'Token token="' + token + '"'} + print user_params + print_and_parse(requests.put(server + '/users/' + user_id + '.json', data = user_params, verify = False, headers = auth_headers)) + return srp.User( login, password, srp.SHA256, srp.NG_1024 ) + + +def authenticate(usr): + session = requests.session() + uname, A = usr.start_authentication() + params = { + 'login': uname, + 'A': binascii.hexlify(A) + } + init = print_and_parse(session.post(server + '/sessions', data = params, verify=False)) + M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) ) + return session.put(server + '/sessions/' + uname, verify = False, + data = {'client_auth': binascii.hexlify(M)}) + +def verify_or_debug(auth, usr): + if ( 'errors' in auth ): + print ' u = "%x"' % usr.u + print ' x = "%x"' % usr.x + print ' v = "%x"' % usr.v + print ' S = "%x"' % usr.S + print ' K = "' + binascii.hexlify(usr.K) + '"' + print ' M = "' + binascii.hexlify(usr.M) + '"' + else: + usr.verify_session( safe_unhexlify(auth["M2"]) ) + +run_tests() diff --git a/test/integration/api/python/login_wrong_username.py b/test/integration/api/python/login_wrong_username.py new file mode 100755 index 0000000..390f250 --- /dev/null +++ b/test/integration/api/python/login_wrong_username.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +server = 'http://localhost:3000' + +import requests +import json +import string +import random + +def id_generator(size=6, chars=string.ascii_uppercase + string.digits): + return ''.join(random.choice(chars) for x in range(size)) + +params = { + 'login': 'python_test_user_'+id_generator(), + 'A': '12345', + } +r = requests.post(server + '/sessions', data = params) +print r.url +print r.text diff --git a/test/integration/api/python/signup.py b/test/integration/api/python/signup.py new file mode 100755 index 0000000..0d3a4e0 --- /dev/null +++ b/test/integration/api/python/signup.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +server = 'http://localhost:3000' + +import requests +import json +import string +import random + +def id_generator(size=6, chars=string.ascii_uppercase + string.digits): + return ''.join(random.choice(chars) for x in range(size)) + +user_params = { + 'user[login]': 'python_test_user_'+id_generator(), + 'user[password_verifier]': '12345', + 'user[password_salt]': '54321' + } +r = requests.post(server + '/users.json', data = user_params) +print r.url +print r.text diff --git a/test/integration/api/python/signup_and_login.py b/test/integration/api/python/signup_and_login.py new file mode 100755 index 0000000..ac611d7 --- /dev/null +++ b/test/integration/api/python/signup_and_login.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +# FAILS +# +# This test is currently failing for me because the session is not kept. +# Played with it a bunch - is probably messed up right now as well. + + +server = 'http://localhost:3000' + +import requests +import json +import string +import random + +def id_generator(size=6, chars=string.ascii_uppercase + string.digits): + return ''.join(random.choice(chars) for x in range(size)) + +def print_and_parse(response): + print response.request.method + ': ' + response.url + print " " + json.dumps(response.request.data) + print " -> " + response.text + return json.loads(response.text) + +def signup(session): + user_params = { + 'user[login]': id_generator(), + 'user[password_verifier]': '12345', + 'user[password_salt]': 'AB54321' + } + return session.post(server + '/users.json', data = user_params) + +def authenticate(session, login): + params = { + 'login': login, + 'A': '12345', + } + init = print_and_parse(session.post(server + '/sessions', data = params)) + return session.put(server + '/sessions/' + login, data = {'client_auth': '123'}) + +session = requests.session() +user = print_and_parse(signup(session)) +# SRP signup would happen here and calculate M hex +auth = print_and_parse(authenticate(session, user['login'])) diff --git a/test/integration/api/python/signup_and_login_wrong_password.py b/test/integration/api/python/signup_and_login_wrong_password.py new file mode 100755 index 0000000..9efffa1 --- /dev/null +++ b/test/integration/api/python/signup_and_login_wrong_password.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +server = 'http://localhost:9292' + +import requests +import json +import string +import random + +def id_generator(size=6, chars=string.ascii_uppercase + string.digits): + return ''.join(random.choice(chars) for x in range(size)) + +def print_and_parse(response): + print response.request.method + ': ' + response.url + print " " + json.dumps(response.request.data) + print " -> " + response.text +# print " () " + json.dumps(requests.utils.dict_from_cookiejar(response.cookies)) + return json.loads(response.text) + +def signup(): + user_params = { + 'user[login]': id_generator(), + 'user[password_verifier]': '12345', + 'user[password_salt]': '54321' + } + return requests.post(server + '/users.json', data = user_params) + +def handshake(login): + params = { + 'login': login, + 'A': '12345', + } + return requests.post(server + '/sessions', data = params) + +def authenticate(login, M): + return requests.put(server + '/sessions/' + login, data = {'M': M}) + + +user = print_and_parse(signup()) +handshake = print_and_parse(handshake(user['login'])) +# SRP signup would happen here and calculate M hex +M = '123ABC' +auth = print_and_parse(authenticate(user['login'], M)) diff --git a/test/integration/api/python/umlauts.py b/test/integration/api/python/umlauts.py new file mode 100755 index 0000000..96fecbf --- /dev/null +++ b/test/integration/api/python/umlauts.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# coding: utf-8 + +# under development + +import requests +import json +import string +import random +import srp._pysrp as srp +import binascii + +safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x) + +# using globals for now +# server = 'https://dev.bitmask.net/1' +server = 'http://api.lvh.me:3000/1' + +def run_tests(): + login = 'test_' + id_generator() + password = id_generator() + "äöì" + id_generator() + usr = srp.User( login, password, srp.SHA256, srp.NG_1024 ) + print_and_parse(signup(login, password)) + + auth = print_and_parse(authenticate(usr)) + verify_or_debug(auth, usr) + assert usr.authenticated() + + +# let's have some random name +def id_generator(size=6, chars=string.ascii_lowercase + string.digits): + return ''.join(random.choice(chars) for x in range(size)) + +# log the server communication +def print_and_parse(response): + request = response.request + print request.method + ': ' + response.url + if hasattr(request, 'data'): + print " " + json.dumps(response.request.data) + print " -> " + response.text + try: + return json.loads(response.text) + except ValueError: + return None + +def signup(login, password): + salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 ) + user_params = { + 'user[login]': login, + 'user[password_verifier]': binascii.hexlify(vkey), + 'user[password_salt]': binascii.hexlify(salt) + } + print json.dumps(user_params) + return requests.post(server + '/users.json', data = user_params, verify = False) + +def authenticate(usr): + session = requests.session() + uname, A = usr.start_authentication() + params = { + 'login': uname, + 'A': binascii.hexlify(A) + } + init = print_and_parse(session.post(server + '/sessions', data = params, verify=False)) + M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) ) + return session.put(server + '/sessions/' + uname, verify = False, + data = {'client_auth': binascii.hexlify(M)}) + +def verify_or_debug(auth, usr): + if ( 'errors' in auth ): + print ' u = "%x"' % usr.u + print ' x = "%x"' % usr.x + print ' v = "%x"' % usr.v + print ' S = "%x"' % usr.S + print ' K = "' + binascii.hexlify(usr.K) + '"' + print ' M = "' + binascii.hexlify(usr.M) + '"' + else: + usr.verify_session( safe_unhexlify(auth["M2"]) ) + +run_tests() diff --git a/test/integration/api/signup_test.rb b/test/integration/api/signup_test.rb new file mode 100644 index 0000000..236c547 --- /dev/null +++ b/test/integration/api/signup_test.rb @@ -0,0 +1,20 @@ +require 'test_helper' +require_relative 'srp_test' + +class SignupTest < SrpTest + + setup do + register_user + end + + test "signup response" do + assert_json_response :login => @login, :ok => true + assert last_response.successful? + end + + test "signup creates user" do + assert @user + assert_equal @login, @user.login + end +end + diff --git a/test/integration/api/srp_test.rb b/test/integration/api/srp_test.rb new file mode 100644 index 0000000..946450e --- /dev/null +++ b/test/integration/api/srp_test.rb @@ -0,0 +1,104 @@ +class SrpTest < RackTest + + teardown do + if @user + cleanup_user + end + Warden.test_reset! + end + + # this test wraps the api and implements the interface the ruby-srp client. + def handshake(login, aa) + post "http://api.lvh.me:3000/1/sessions.json", + :login => login, + 'A' => aa, + :format => :json + response = JSON.parse(last_response.body) + if response['errors'] + raise RECORD_NOT_FOUND.new(response['errors']) + else + return response['B'] + end + end + + def validate(m) + put "http://api.lvh.me:3000/1/sessions/" + @login + '.json', + :client_auth => m, + :format => :json + return JSON.parse(last_response.body) + end + + protected + + attr_reader :server_auth + + def register_user(login = "integration_test_user", password = 'srp, verify me!') + cleanup_user(login) + post 'http://api.lvh.me:3000/1/users.json', + user_params(login: login, password: password) + @user = User.find_by_login(login) + @login = login + @password = password + end + + def update_user(params) + put "http://api.lvh.me:3000/1/users/" + @user.id + '.json', + user_params(params), + auth_headers + end + + def authenticate(params = nil) + @server_auth = srp(params).authenticate(self) + end + + def auth_headers + return {} if @server_auth.nil? + { + "HTTP_AUTHORIZATION" => encoded_token + } + end + + def encoded_token + ActionController::HttpAuthentication::Token.encode_credentials(server_auth["token"]) + end + + def logout(params=nil, headers=nil) + delete "http://api.lvh.me:3000/1/logout.json", + params || {format: :json}, + headers || auth_headers + end + + def cleanup_user(login = nil) + login ||= @user.login + Identity.by_address.key(login + '@' + APP_CONFIG[:domain]).each do |identity| + identity.destroy + end + if user = User.find_by_login(login) + user.destroy + end + end + + def user_params(params) + if params.keys.include?(:password) + srp_process_password(params) + end + return { user: params, format: :json } + end + + def srp_process_password(params) + params.reverse_merge! login: @login, salt: @salt + @srp = SRP::Client.new params[:login], password: params.delete(:password) + @salt = srp.salt.to_s(16) + params.merge! :password_verifier => srp.verifier.to_s(16), + :password_salt => @salt + end + + def srp(params = nil) + if params.nil? + @srp + else + params.reverse_merge! password: @password + SRP::Client.new(params.delete(:login) || @login, params) + end + end +end diff --git a/test/integration/api/update_account_test.rb b/test/integration/api/update_account_test.rb new file mode 100644 index 0000000..63429e7 --- /dev/null +++ b/test/integration/api/update_account_test.rb @@ -0,0 +1,51 @@ +require 'test_helper' +require_relative 'srp_test' + +class UpdateAccountTest < SrpTest + + setup do + register_user + end + + test "require authentication" do + update_user password: "No! Verify me instead." + assert_access_denied + end + + test "require token" do + authenticate + put "http://api.lvh.me:3000/1/users/" + @user.id + '.json', + user_params(password: "No! Verify me instead.") + assert_access_denied + end + + test "update password via api" do + authenticate + update_user password: "No! Verify me instead." + authenticate + assert last_response.successful? + assert_nil server_auth["errors"] + assert server_auth["M2"] + end + + test "change login with password_verifier" do + authenticate + new_login = 'zaph' + cleanup_user new_login + update_user login: new_login, password: @password + authenticate + assert last_response.successful? + assert_equal new_login, @user.reload.login + end + + test "prevent changing login without changing password_verifier" do + authenticate + original_login = @user.login + new_login = 'zaph' + cleanup_user new_login + update_user login: new_login + assert last_response.successful? + # does not change login if no password_verifier is present + assert_equal original_login, @user.reload.login + end +end diff --git a/test/integration/browser/account_test.rb b/test/integration/browser/account_test.rb new file mode 100644 index 0000000..a5677ad --- /dev/null +++ b/test/integration/browser/account_test.rb @@ -0,0 +1,147 @@ +require 'test_helper' + +class AccountTest < BrowserIntegrationTest + + teardown do + Identity.destroy_all_disabled + end + + test "normal account workflow" do + username, password = submit_signup + assert page.has_content?("Welcome #{username}") + click_on 'Logout' + assert page.has_content?("Log In") + assert_equal '/', current_path + assert user = User.find_by_login(username) + user.account.destroy + end + + test "successful login" do + username, password = submit_signup + click_on 'Logout' + attempt_login(username, password) + assert page.has_content?("Welcome #{username}") + within('.sidenav li.active') do + assert page.has_content?("Overview") + end + User.find_by_login(username).account.destroy + end + + test "failed login" do + visit '/' + attempt_login("username", "wrong password") + assert_invalid_login(page) + end + + test "account destruction" do + username, password = submit_signup + click_on I18n.t('account_settings') + click_on I18n.t('destroy_my_account') + assert page.has_content?(I18n.t('account_destroyed')) + attempt_login(username, password) + assert_invalid_login(page) + end + + test "handle blocked after account destruction" do + username, password = submit_signup + click_on I18n.t('account_settings') + click_on I18n.t('destroy_my_account') + submit_signup(username) + assert page.has_content?('has already been taken') + end + + test "default user actions" do + username, password = submit_signup + click_on "Account Settings" + assert page.has_content? I18n.t('destroy_my_account') + assert page.has_no_css? '#update_login_and_password' + assert page.has_no_css? '#update_pgp_key' + end + + test "default admin actions" do + username, password = submit_signup + with_config admins: [username] do + click_on "Account Settings" + assert page.has_content? I18n.t('destroy_my_account') + assert page.has_no_css? '#update_login_and_password' + assert page.has_css? '#update_pgp_key' + end + end + + test "change password" do + with_config user_actions: ['change_password'] do + username, password = submit_signup + click_on "Account Settings" + within('#update_login_and_password') do + fill_in 'Password', with: "other password" + fill_in 'Password confirmation', with: "other password" + click_on 'Save' + end + click_on 'Logout' + attempt_login(username, "other password") + assert page.has_content?("Welcome #{username}") + User.find_by_login(username).account.destroy + end + end + + test "change pgp key" do + with_config user_actions: ['change_pgp_key'] do + pgp_key = FactoryGirl.build :pgp_key + username, password = submit_signup + click_on "Account Settings" + within('#update_pgp_key') do + fill_in 'Public key', with: pgp_key + click_on 'Save' + end + page.assert_selector 'input[value="Saving..."]' + # at some point we're done: + page.assert_no_selector 'input[value="Saving..."]' + assert page.has_field? 'Public key', with: pgp_key.to_s + user = User.find_by_login(username) + assert_equal pgp_key, user.public_key + user.account.destroy + end + end + + + # trying to seed an invalid A for srp login + test "detects attempt to circumvent SRP" do + user = FactoryGirl.create :user + visit '/login' + fill_in 'Username', with: user.login + fill_in 'Password', with: "password" + inject_malicious_js + click_on 'Log In' + assert page.has_content?("Invalid random key") + assert page.has_no_content?("Welcome") + user.destroy + end + + test "reports internal server errors" do + V1::UsersController.any_instance.stubs(:create).raises + submit_signup + assert page.has_content?("server failed") + end + + def attempt_login(username, password) + click_on 'Log In' + fill_in 'Username', with: username + fill_in 'Password', with: password + click_on 'Log In' + end + + def assert_invalid_login(page) + assert page.has_selector? 'input.btn-primary.disabled' + assert page.has_content? I18n.t(:invalid_user_pass) + assert page.has_no_selector? 'input.btn-primary.disabled' + end + + def inject_malicious_js + page.execute_script <<-EOJS + var calc = new srp.Calculate(); + calc.A = function(_a) {return "00";}; + calc.S = calc.A; + srp.session = new srp.Session(null, calc); + EOJS + end +end diff --git a/test/integration/browser/session_test.rb b/test/integration/browser/session_test.rb new file mode 100644 index 0000000..3a41b3a --- /dev/null +++ b/test/integration/browser/session_test.rb @@ -0,0 +1,27 @@ +require 'test_helper' + +class SessionTest < BrowserIntegrationTest + + setup do + @username, password = submit_signup + end + + teardown do + user = User.find_by_login(@username) + id = user.identity + id.destroy + user.destroy + end + + test "valid session" do + assert page.has_content?("Welcome #{@username}") + end + + test "expired session" do + assert page.has_content?("Welcome #{@username}") + pretend_now_is(Time.now + 40.minutes) do + visit '/' + assert page.has_no_content?("Welcome #{@username}") + end + end +end diff --git a/test/integration/navigation_test.rb b/test/integration/navigation_test.rb new file mode 100644 index 0000000..eec8c0e --- /dev/null +++ b/test/integration/navigation_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class NavigationTest < ActionDispatch::IntegrationTest + + # test "the truth" do + # assert true + # end +end + diff --git a/test/leap_web_users_test.rb b/test/leap_web_users_test.rb new file mode 100644 index 0000000..f142e54 --- /dev/null +++ b/test/leap_web_users_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class LeapWebUsersTest < ActiveSupport::TestCase + test "module exists" do + assert_kind_of Module, LeapWebUsers + end +end diff --git a/test/support/auth_test_helper.rb b/test/support/auth_test_helper.rb new file mode 100644 index 0000000..57f9f9b --- /dev/null +++ b/test/support/auth_test_helper.rb @@ -0,0 +1,65 @@ +module AuthTestHelper + extend ActiveSupport::Concern + + # Controller will fetch current user from warden. + # Make it pick up our current_user + included do + setup do + request.env['warden'] ||= stub :user => nil + end + end + + def login(user_or_method_hash = {}) + if user_or_method_hash.respond_to?(:reverse_merge) + user_or_method_hash.reverse_merge! :is_admin? => false + end + @current_user = stub_record(:user, user_or_method_hash) + request.env['warden'] = stub :user => @current_user + request.env['HTTP_AUTHORIZATION'] = header_for_token_auth + return @current_user + end + + def assert_access_denied(denied = true, logged_in = true) + if denied + if @response.content_type == 'application/json' + assert_json_response('error' => I18n.t(:not_authorized)) + assert_response :unprocessable_entity + else + if logged_in + assert_equal({:alert => I18n.t(:not_authorized)}, flash.to_hash) + assert_redirected_to home_url + else + assert_equal({:alert => I18n.t(:not_authorized_login)}, flash.to_hash) + assert_redirected_to login_url + end + end + else + assert flash[:alert].blank? + end + end + + def expect_logout + expect_warden_logout + @token.expects(:destroy) if @token + end + + protected + + def header_for_token_auth + @token = find_record(:token, :authenticate => @current_user) + ActionController::HttpAuthentication::Token.encode_credentials @token.id + end + + def expect_warden_logout + raw = mock('raw session') do + expects(:inspect) + end + request.env['warden'].expects(:raw_session).returns(raw) + request.env['warden'].expects(:logout) + end + +end + +class ActionController::TestCase + include AuthTestHelper +end diff --git a/test/support/stub_record_helper.rb b/test/support/stub_record_helper.rb new file mode 100644 index 0000000..25138a0 --- /dev/null +++ b/test/support/stub_record_helper.rb @@ -0,0 +1,53 @@ +module StubRecordHelper + + # + # We will stub find when called on the records class and + # return the record given. + # + # If no record is given but a hash or nil will create a stub based on + # that instead and returns the stub. + # + def find_record(factory, record_or_attribs_hash = {}) + record = stub_record factory, record_or_attribs_hash, true + klass = record.class + # find is just an alias for get with CouchRest Model + klass.stubs(:get).with(record.to_param.to_s).returns(record) + klass.stubs(:find).with(record.to_param.to_s).returns(record) + return record + end + + # Create a stub that has the usual functions of a database record. + # It won't fail on rendering a form for example. + # + # If the second parameter is a record we return the record itself. + # This way you can build functions that either take a record or a + # method hash to stub from. See find_record for an example. + def stub_record(factory, record_or_method_hash = {}, persisted=false) + if record_or_method_hash && !record_or_method_hash.is_a?(Hash) + return record_or_method_hash + end + FactoryGirl.build_stubbed(factory).tap do |record| + if persisted or record.persisted? + record_or_method_hash.reverse_merge! :created_at => Time.now, + :updated_at => Time.now, :id => Random.rand(100000).to_s + end + record.stubs(record_or_method_hash) if record_or_method_hash.present? + end + end + + # returns deep stringified attributes so they can be compared to + # what the controller receives as params + def record_attributes_for(factory, attribs_hash = nil) + FactoryGirl.attributes_for(factory, attribs_hash).tap do |attribs| + attribs.keys.each do |key| + val = attribs.delete(key) + attribs[key.to_s] = val.is_a?(Hash) ? val.stringify_keys! : val + end + end + end + +end + +class ActionController::TestCase + include StubRecordHelper +end diff --git a/test/support/time_test_helper.rb b/test/support/time_test_helper.rb new file mode 100644 index 0000000..f673f12 --- /dev/null +++ b/test/support/time_test_helper.rb @@ -0,0 +1,30 @@ +# Extend the Time class so that we can offset the time that 'now' +# returns. This should allow us to effectively time warp for functional +# tests that require limits per hour, what not. +class Time #:nodoc: + class < 0 + Identity.destroy_all_disabled + assert_equal 0, Identity.disabled.count + end + + def alias_name + @alias_name ||= Faker::Internet.user_name + end + + def forward_address + @forward_address ||= Faker::Internet.email + end + + def pgp_key_string + @pgp_key ||= "DUMMY PGP KEY ... "+SecureRandom.base64(4096) + end +end diff --git a/test/unit/local_email_test.rb b/test/unit/local_email_test.rb new file mode 100644 index 0000000..20ee7f1 --- /dev/null +++ b/test/unit/local_email_test.rb @@ -0,0 +1,65 @@ +require 'test_helper' + +class LocalEmailTest < ActiveSupport::TestCase + + test "appends domain" do + local = LocalEmail.new(handle) + assert_equal LocalEmail.new(email), local + assert local.valid? + end + + test "returns handle" do + local = LocalEmail.new(email) + assert_equal handle, local.handle + end + + test "prints full email" do + local = LocalEmail.new(handle) + assert_equal email, "#{local}" + end + + test "validates domain" do + local = LocalEmail.new(Faker::Internet.email) + assert !local.valid? + assert_equal ["needs to end in @#{LocalEmail.domain}"], local.errors[:email] + end + + test "blacklists rfc2142" do + black_listed = LocalEmail.new('hostmaster') + assert !black_listed.valid? + end + + test "blacklists etc passwd" do + black_listed = LocalEmail.new('nobody') + assert !black_listed.valid? + end + + test "whitelist overwrites automatic blacklists" do + with_config handle_whitelist: ['nobody', 'hostmaster'] do + white_listed = LocalEmail.new('nobody') + assert white_listed.valid? + white_listed = LocalEmail.new('hostmaster') + assert white_listed.valid? + end + end + + test "blacklists from config" do + black_listed = LocalEmail.new('www-data') + assert !black_listed.valid? + end + + test "blacklist from config overwrites whitelist" do + with_config handle_whitelist: ['www-data'] do + black_listed = LocalEmail.new('www-data') + assert !black_listed.valid? + end + end + + def handle + @handle ||= Faker::Internet.user_name + end + + def email + handle + "@" + APP_CONFIG[:domain] + end +end diff --git a/test/unit/token_test.rb b/test/unit/token_test.rb new file mode 100644 index 0000000..a3c6cf6 --- /dev/null +++ b/test/unit/token_test.rb @@ -0,0 +1,89 @@ +require 'test_helper' + +class ClientCertificateTest < ActiveSupport::TestCase + include StubRecordHelper + + setup do + @user = find_record :user + end + + test "new token for user" do + sample = Token.new(:user_id => @user.id) + assert sample.valid? + assert_equal @user.id, sample.user_id + assert_equal @user, sample.authenticate + end + + test "token id is secure" do + sample = Token.new(:user_id => @user.id) + other = Token.new(:user_id => @user.id) + assert sample.id, + "id is set on initialization" + assert sample.id[0..10] != other.id[0..10], + "token id prefixes should not repeat" + assert /[g-zG-Z]/.match(sample.id), + "should use non hex chars in the token id" + assert sample.id.size > 16, + "token id should be more than 16 chars long" + end + + test "token checks for user" do + sample = Token.new + assert !sample.valid?, "Token should require a user record" + end + + test "token updates timestamps" do + sample = Token.new(user_id: @user.id) + sample.last_seen_at = 1.minute.ago + sample.expects(:save) + assert_equal @user, sample.authenticate + assert Time.now - sample.last_seen_at < 1.minute, "last_seen_at has not been updated" + end + + test "token will not expire if token_expires_after is not set" do + sample = Token.new(user_id: @user.id) + sample.last_seen_at = 2.years.ago + with_config auth: {} do + sample.expects(:save) + assert_equal @user, sample.authenticate + end + end + + test "expired token returns nil on authenticate" do + sample = Token.new(user_id: @user.id) + sample.last_seen_at = 2.hours.ago + with_config auth: {token_expires_after: 60} do + sample.expects(:destroy) + assert_nil sample.authenticate + end + end + + test "Token.destroy_all_expired is noop if no expiry is set" do + expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago + with_config auth: {} do + Token.destroy_all_expired + end + assert_equal expired, Token.find(expired.id) + end + + test "Token.destroy_all_expired cleans up expired tokens only" do + expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago + fresh = FactoryGirl.create :token + with_config auth: {token_expires_after: 60} do + Token.destroy_all_expired + end + assert_nil Token.find(expired.id) + assert_equal fresh, Token.find(fresh.id) + fresh.destroy + end + + + test "Token.destroy_all_expired does not interfere with expired.authenticate" do + expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago + with_config auth: {token_expires_after: 60} do + Token.destroy_all_expired + end + assert_nil expired.authenticate + end + +end diff --git a/test/unit/unauthenticated_user_test.rb b/test/unit/unauthenticated_user_test.rb new file mode 100644 index 0000000..e5fafb8 --- /dev/null +++ b/test/unit/unauthenticated_user_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class UnauthenticatedUserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb new file mode 100644 index 0000000..ffbb7d8 --- /dev/null +++ b/test/unit/user_test.rb @@ -0,0 +1,68 @@ +require 'test_helper' + +class UserTest < ActiveSupport::TestCase + + include SRP::Util + setup do + @user = FactoryGirl.build(:user) + end + + test "design docs in database are authorative" do + assert !User.design_doc.auto_update, + "Automatic update of design docs should be disabled" + end + + test "test set of attributes should be valid" do + @user.valid? + assert_equal Hash.new, @user.errors.messages + end + + test "test require hex for password_verifier" do + @user.password_verifier = "QWER" + assert !@user.valid? + end + + test "test require alphanumerical for login" do + @user.login = "qw#r" + assert !@user.valid? + end + + test "verifier returns number for the hex in password_verifier" do + assert_equal @user.password_verifier.hex, @user.verifier + end + + test "salt returns number for the hex in password_salt" do + assert_equal @user.password_salt.hex, @user.salt + end + + test 'normal user is no admin' do + assert !@user.is_admin? + end + + test 'user with login in APP_CONFIG is an admin' do + admin_login = APP_CONFIG['admins'].first + @user.login = admin_login + assert @user.is_admin? + end + + test "login needs to be unique" do + other_user = FactoryGirl.create :user, login: @user.login + assert !@user.valid? + other_user.destroy + end + + test "login needs to be unique amongst aliases" do + other_user = FactoryGirl.create :user + id = Identity.create_for other_user, address: @user.login + assert !@user.valid? + id.destroy + other_user.destroy + end + + test "deprecated public key api still works" do + key = SecureRandom.base64(4096) + @user.public_key = key + assert_equal key, @user.public_key + end + +end diff --git a/test/unit/warden_strategy_secure_remote_password_test.rb b/test/unit/warden_strategy_secure_remote_password_test.rb new file mode 100644 index 0000000..e6fcfbe --- /dev/null +++ b/test/unit/warden_strategy_secure_remote_password_test.rb @@ -0,0 +1,63 @@ +class WardenStrategySecureRemotePasswordTest < ActiveSupport::TestCase + +# TODO : turn this into sth. real +=begin + setup do + @user = stub :login => "me", :id => 123 + @client_hex = 'a123' + @client_rnd = @client_hex.hex + @server_hex = 'b123' + @server_rnd = @server_hex.hex + @server_rnd_exp = 'e123'.hex + @salt = 'stub user salt' + @server_handshake = stub :aa => @client_rnd, :bb => @server_rnd, :b => @server_rnd_exp + @server_auth = 'adfe' + end + + + test "should perform handshake" do + @user.expects(:initialize_auth). + with(@client_rnd). + returns(@server_handshake) + @server_handshake.expects(:to_json). + returns({'B' => @server_hex, 'salt' => @salt}.to_json) + User.expects(:find).with(@user.login).returns(@user) + assert_equal @server_handshake, session[:handshake] + assert_response :success + assert_json_response :B => @server_hex, :salt => @salt + end + + test "should report user not found" do + unknown = "login_that_does_not_exist" + User.expects(:find).with(unknown).raises(RECORD_NOT_FOUND) + post :create, :login => unknown + assert_response :success + assert_json_error "login" => ["unknown user"] + end + + test "should authorize" do + session[:handshake] = @server_handshake + @server_handshake.expects(:authenticate!). + with(@client_rnd). + returns(@user) + @server_handshake.expects(:to_json). + returns({:M2 => @server_auth}.to_json) + post :update, :id => @user.login, :client_auth => @client_hex + assert_nil session[:handshake] + assert_json_response :M2 => @server_auth + assert_equal @user.id, session[:user_id] + end + + test "should report wrong password" do + session[:handshake] = @server_handshake + @server_handshake.expects(:authenticate!). + with(@client_rnd). + raises(WRONG_PASSWORD) + post :update, :id => @user.login, :client_auth => @client_hex + assert_nil session[:handshake] + assert_nil session[:user_id] + assert_json_error "password" => ["wrong password"] + end + +=end +end diff --git a/test/unit/webfinger/host_meta_presenter_test.rb b/test/unit/webfinger/host_meta_presenter_test.rb new file mode 100644 index 0000000..af86404 --- /dev/null +++ b/test/unit/webfinger/host_meta_presenter_test.rb @@ -0,0 +1,24 @@ +require 'test_helper' +require 'webfinger' +require 'json' + +class Webfinger::HostMetaPresenterTest < ActiveSupport::TestCase + + setup do + @request = stub( + url: "https://#{APP_CONFIG[:domain]}/.well-known/host-meta" + ) + @meta = Webfinger::HostMetaPresenter.new(@request) + end + + test "creates proper json" do + hash = JSON.parse @meta.to_json + assert_equal ["subject", "links"].sort, hash.keys.sort + hash.each do |key, value| + assert_equal @meta.send(key.to_sym).to_json, value.to_json + end + end + +end + + diff --git a/test/unit/webfinger/user_presenter_test.rb b/test/unit/webfinger/user_presenter_test.rb new file mode 100644 index 0000000..04aeb22 --- /dev/null +++ b/test/unit/webfinger/user_presenter_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' +require 'webfinger' +require 'json' + +class Webfinger::UserPresenterTest < ActiveSupport::TestCase + + + setup do + @user = stub( + username: 'testuser', + email_address: "testuser@#{APP_CONFIG[:domain]}" + ) + @request = stub( + host: APP_CONFIG[:domain] + ) + end + + test "user without key has no links" do + @user.stubs :public_key => nil + presenter = Webfinger::UserPresenter.new(@user, @request) + assert_equal Hash.new, presenter.links + end + + test "user with key has corresponding link" do + @user.stubs :public_key => "here's a key" + presenter = Webfinger::UserPresenter.new(@user, @request) + assert_equal [:public_key], presenter.links.keys + assert_equal "PGP", presenter.links[:public_key][:type] + assert_equal presenter.send(:key), presenter.links[:public_key][:href] + end + + test "key is base64 encoded" do + @user.stubs :public_key => "here's a key" + presenter = Webfinger::UserPresenter.new(@user, @request) + assert_equal Base64.encode64(@user.public_key), presenter.send(:key) + end + + test "creates proper json representation" do + @user.stubs :public_key => "here's a key" + presenter = Webfinger::UserPresenter.new(@user, @request) + hash = JSON.parse presenter.to_json + assert_equal ["subject", "links"].sort, hash.keys.sort + hash.each do |key, value| + assert_equal presenter.send(key.to_sym).to_json, value.to_json + end + end + + +end diff --git a/users/app/assets/images/leap_web_users/.gitkeep b/users/app/assets/images/leap_web_users/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/app/assets/javascripts/leap_web_users/.gitkeep b/users/app/assets/javascripts/leap_web_users/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/app/assets/javascripts/srp b/users/app/assets/javascripts/srp deleted file mode 160000 index 8f33d32..0000000 --- a/users/app/assets/javascripts/srp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8f33d32d40b1e21ae7fb9a92c78a275422af4217 diff --git a/users/app/assets/javascripts/users.js b/users/app/assets/javascripts/users.js deleted file mode 100644 index 8486756..0000000 --- a/users/app/assets/javascripts/users.js +++ /dev/null @@ -1,132 +0,0 @@ -(function() { - // - // LOCAL FUNCTIONS - // - - var poll_users, - prevent_default, - form_failed, - form_passed, - clear_errors, - update_user; - - prevent_default = function(event) { - return event.preventDefault(); - }; - - poll_users = function(query, process) { - return $.get("/1/users.json", { - query: query - }).done(process); - }; - - clear_errors = function() { - return $('#messages').empty(); - }; - - update_user = function(submitEvent) { - var form = submitEvent.target; - var token = form.dataset.token; - var url = form.action; - var req = $.ajax({ - url: url, - type: 'PUT', - headers: { Authorization: 'Token token="' + token + '"' }, - data: $(form).serialize() - }); - req.done( function() { - $(form).find('input[type="submit"]').button('reset'); - }); - }; - - markAsSubmitted = function(submitEvent) { - var form = submitEvent.target; - $(form).addClass('submitted') - // bootstrap loading state: - $(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 - // - - srp.session = new srp.Session(); - - srp.signedUp = function() { - return srp.login(); - }; - - srp.loggedIn = function() { - return window.location = '/'; - }; - - srp.updated = function() { - return window.location = '/users/' + srp.session.id(); - }; - - // - // if a json request returns an error, this function gets called and - // decorates the appropriate fields with the error messages. - // - srp.error = function(message) { - clear_errors(); - var errors = extractErrors(message); - displayErrors(errors); - resetButtons(); - } - - function extractErrors(message) { - if ($.isPlainObject(message) && message.errors) { - return message.errors; - } else { - 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); - } - }; - - // - // INIT - // - - $(document).ready(function() { - $('form').submit(markAsSubmitted); - $('#new_user').submit(prevent_default); - $('#new_user').submit(srp.signup); - $('#new_session').submit(prevent_default); - $('#new_session').submit(srp.login); - $('#update_login_and_password').submit(prevent_default); - $('#update_login_and_password').submit(srp.update); - $('#update_pgp_key').submit(prevent_default); - $('#update_pgp_key').submit(update_user); - return $('#user-typeahead').typeahead({ - source: poll_users - }); - }); - -}).call(this); diff --git a/users/app/assets/stylesheets/leap_web_users/.gitkeep b/users/app/assets/stylesheets/leap_web_users/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/app/controllers/.gitkeep b/users/app/controllers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/app/controllers/account_settings_controller.rb b/users/app/controllers/account_settings_controller.rb deleted file mode 100644 index e69de29..0000000 diff --git a/users/app/controllers/controller_extension/authentication.rb b/users/app/controllers/controller_extension/authentication.rb deleted file mode 100644 index 03d3989..0000000 --- a/users/app/controllers/controller_extension/authentication.rb +++ /dev/null @@ -1,75 +0,0 @@ -module ControllerExtension::Authentication - extend ActiveSupport::Concern - - private - - included do - helper_method :current_user, :logged_in?, :admin? - end - - def current_user - @current_user ||= token_authenticate || warden.user - end - - def logged_in? - !!current_user - end - - def require_login - access_denied unless logged_in? - end - - # some actions only make sense if you are not logged in yet. - # (login, signup). If a user tries to perform these they will - # be redirected to their dashboard. - def redirect_if_logged_in - redirect_to home_url if logged_in? - end - - def access_denied - respond_to do |format| - format.html do - if logged_in? - redirect_to home_url, :alert => t(:not_authorized) - else - redirect_to login_url, :alert => t(:not_authorized_login) - end - end - format.json do - render :json => {'error' => t(:not_authorized)}, status: :unprocessable_entity - end - end - end - - def admin? - current_user && current_user.is_admin? - end - - def require_admin - access_denied unless admin? - end - - def authentication_errors - return unless attempted_login? - errors = get_warden_errors - errors.inject({}) do |translated,err| - translated[err.first] = I18n.t(err.last) - translated - end - end - - def get_warden_errors - if strategy = warden.winning_strategy - message = strategy.message - # in case we get back the default message to fail! - message.respond_to?(:inject) ? message : { base: message } - else - { login: :all_strategies_failed } - end - end - - def attempted_login? - request.env['warden.options'] && - request.env['warden.options'][:attempted_path] - end -end diff --git a/users/app/controllers/controller_extension/token_authentication.rb b/users/app/controllers/controller_extension/token_authentication.rb deleted file mode 100644 index 6e0a6ce..0000000 --- a/users/app/controllers/controller_extension/token_authentication.rb +++ /dev/null @@ -1,27 +0,0 @@ -module ControllerExtension::TokenAuthentication - extend ActiveSupport::Concern - - def token - @token ||= authenticate_with_http_token do |token_id, options| - Token.find(token_id) - end - end - - def token_authenticate - @token_authenticated ||= token.authenticate if token - end - - def require_token - access_denied unless token_authenticate - end - - def logout - super - clear_token - end - - def clear_token - token.destroy if token - end -end - diff --git a/users/app/controllers/keys_controller.rb b/users/app/controllers/keys_controller.rb deleted file mode 100644 index fb28901..0000000 --- a/users/app/controllers/keys_controller.rb +++ /dev/null @@ -1,18 +0,0 @@ -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/sessions_controller.rb b/users/app/controllers/sessions_controller.rb deleted file mode 100644 index 8919a4d..0000000 --- a/users/app/controllers/sessions_controller.rb +++ /dev/null @@ -1,28 +0,0 @@ -class SessionsController < ApplicationController - - before_filter :redirect_if_logged_in, :only => [:new] - - def new - @session = Session.new - if authentication_errors - @errors = authentication_errors - render :status => 422 - end - end - - def destroy - logout - redirect_to home_url - end - - # - # 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}" - # throw :warden, response.finish - #end - -end diff --git a/users/app/controllers/users_base_controller.rb b/users/app/controllers/users_base_controller.rb deleted file mode 100644 index 9becf0d..0000000 --- a/users/app/controllers/users_base_controller.rb +++ /dev/null @@ -1,18 +0,0 @@ -# -# common base class for all user related controllers -# - -class UsersBaseController < ApplicationController - - protected - - def fetch_user - @user = User.find(params[:user_id] || params[:id]) - if !@user && admin? - redirect_to users_url, :alert => t(:no_such_thing, :thing => 'user') - elsif !admin? && @user != current_user - access_denied - end - end - -end diff --git a/users/app/controllers/users_controller.rb b/users/app/controllers/users_controller.rb deleted file mode 100644 index c8e09b6..0000000 --- a/users/app/controllers/users_controller.rb +++ /dev/null @@ -1,69 +0,0 @@ -# -# This is an HTML-only controller. For the JSON-only controller, see v1/users_controller.rb -# - -class UsersController < UsersBaseController - - before_filter :require_login, :except => [:new] - before_filter :redirect_if_logged_in, :only => [:new] - before_filter :require_admin, :only => [:index, :deactivate, :enable] - before_filter :fetch_user, :only => [:show, :edit, :update, :destroy, :deactivate, :enable] - - respond_to :html - - def index - if params[:query] - if @user = User.find_by_login(params[:query]) - redirect_to @user - return - else - @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ) - end - else - @users = User.by_created_at.descending - end - @users = @users.limit(100) - end - - def new - @user = User.new - end - - def show - end - - 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 - respond_with @user - end - - def enable - @user.enabled = true - @user.save - respond_with @user - end - - def destroy - @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 bye_url - end - end - -end diff --git a/users/app/controllers/v1/messages_controller.rb b/users/app/controllers/v1/messages_controller.rb deleted file mode 100644 index f71d0f1..0000000 --- a/users/app/controllers/v1/messages_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -module V1 - class MessagesController < ApplicationController - - skip_before_filter :verify_authenticity_token - before_filter :require_token - - respond_to :json - - def index - render json: (current_user ? current_user.messages : [] ) - end - - def update - message = Message.find(params[:id]) - if (message and current_user) - message.mark_as_read_by(current_user) - message.save - render json: true - else - render json: false - end - end - - end -end diff --git a/users/app/controllers/v1/sessions_controller.rb b/users/app/controllers/v1/sessions_controller.rb deleted file mode 100644 index eae3a1e..0000000 --- a/users/app/controllers/v1/sessions_controller.rb +++ /dev/null @@ -1,45 +0,0 @@ -module V1 - class SessionsController < ApplicationController - - skip_before_filter :verify_authenticity_token - before_filter :require_token, only: :destroy - - def new - @session = Session.new - if authentication_errors - @errors = authentication_errors - render :status => 422 - end - end - - def create - logout if logged_in? - if params['A'] - authenticate! - else - @user = User.find_by_login(params['login']) - render :json => {salt: @user.salt} - end - end - - def update - authenticate! - @token = Token.create(:user_id => current_user.id) - session[:token] = @token.id - render :json => login_response - end - - def destroy - logout - head :no_content - end - - protected - - def login_response - handshake = session.delete(:handshake) || {} - handshake.to_hash.merge(:id => current_user.id, :token => @token.id) - end - - end -end diff --git a/users/app/controllers/v1/users_controller.rb b/users/app/controllers/v1/users_controller.rb deleted file mode 100644 index 8897d01..0000000 --- a/users/app/controllers/v1/users_controller.rb +++ /dev/null @@ -1,32 +0,0 @@ -module V1 - class UsersController < UsersBaseController - - skip_before_filter :verify_authenticity_token - before_filter :fetch_user, :only => [:update] - before_filter :require_admin, :only => [:index] - before_filter :require_token, :only => [:update] - - respond_to :json - - # used for autocomplete for admins in the web ui - def index - if params[:query] - @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ) - respond_with @users.map(&:login).sort - else - render :json => {'error' => 'query required', 'status' => :unprocessable_entity} - end - end - - def create - @user = Account.create(params[:user]) - respond_with @user # return ID instead? - end - - def update - @user.account.update params[:user] - respond_with @user - end - - end -end diff --git a/users/app/controllers/webfinger_controller.rb b/users/app/controllers/webfinger_controller.rb deleted file mode 100644 index 8872802..0000000 --- a/users/app/controllers/webfinger_controller.rb +++ /dev/null @@ -1,19 +0,0 @@ -class WebfingerController < ApplicationController - - respond_to :xml, :json - layout false - - def host_meta - @host_meta = Webfinger::HostMetaPresenter.new(request) - respond_with @host_meta - end - - def search - username = params[:q].split('@')[0].to_s.downcase - user = User.find_by_login(username) - raise RECORD_NOT_FOUND, 'User not found' unless user.present? - @presenter = Webfinger::UserPresenter.new(user, request) - respond_with @presenter - end - -end diff --git a/users/app/designs/message/by_user_ids_to_show.js b/users/app/designs/message/by_user_ids_to_show.js deleted file mode 100644 index e33566b..0000000 --- a/users/app/designs/message/by_user_ids_to_show.js +++ /dev/null @@ -1,7 +0,0 @@ -function (doc) { - if (doc.type === 'Message' && doc.user_ids_to_show && Array.isArray(doc.user_ids_to_show)) { - doc.user_ids_to_show.forEach(function (userId) { - emit(userId, 1); - }); - } -} diff --git a/users/app/designs/message/by_user_ids_to_show_and_created_at.js b/users/app/designs/message/by_user_ids_to_show_and_created_at.js deleted file mode 100644 index 54e4604..0000000 --- a/users/app/designs/message/by_user_ids_to_show_and_created_at.js +++ /dev/null @@ -1,9 +0,0 @@ -// not using at moment -// call with something like Message.by_user_ids_to_show_and_created_at.startkey([user_id, start_date]).endkey([user_id,end_date]) -function (doc) { - if (doc.type === 'Message' && doc.user_ids_to_show && Array.isArray(doc.user_ids_to_show)) { - doc.user_ids_to_show.forEach(function (userId) { - emit([userId, doc.created_at], 1); - }); - } -} diff --git a/users/app/designs/user/by_created_at_and_one_month_warning_not_sent.js b/users/app/designs/user/by_created_at_and_one_month_warning_not_sent.js deleted file mode 100644 index 53a95de..0000000 --- a/users/app/designs/user/by_created_at_and_one_month_warning_not_sent.js +++ /dev/null @@ -1,5 +0,0 @@ -function (doc) { - if ((doc['type'] == 'User') && (doc['created_at'] != null) && (doc['one_month_warning_sent'] == null)) { - emit(doc['created_at'], 1); - } -} diff --git a/users/app/helpers/.gitkeep b/users/app/helpers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/app/helpers/email_aliases_helper.rb b/users/app/helpers/email_aliases_helper.rb deleted file mode 100644 index b56b068..0000000 --- a/users/app/helpers/email_aliases_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -module EmailAliasesHelper - - def email_alias_form(options = {}) - simple_form_for [@user, EmailAlias.new()], - :html => {:class => "form-horizontal email-alias form"}, - :validate => true do |f| - yield f - end - end - -end diff --git a/users/app/helpers/sessions_helper.rb b/users/app/helpers/sessions_helper.rb deleted file mode 100644 index 309f8b2..0000000 --- a/users/app/helpers/sessions_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module SessionsHelper -end diff --git a/users/app/helpers/users_helper.rb b/users/app/helpers/users_helper.rb deleted file mode 100644 index f56faab..0000000 --- a/users/app/helpers/users_helper.rb +++ /dev/null @@ -1,14 +0,0 @@ -module UsersHelper - - def user_form_class(*classes) - (classes + ['user', 'form', (@user.new_record? ? 'new' : 'edit')]).compact.join(' ') - end - - def wrapped(item, options = {}) - options[:as] ||= :div - content_tag options[:as], :class => dom_class(item), :id => dom_id(item) do - yield - end - end - -end diff --git a/users/app/mailers/.gitkeep b/users/app/mailers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/app/models/.gitkeep b/users/app/models/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/app/models/account.rb b/users/app/models/account.rb deleted file mode 100644 index cf998e4..0000000 --- a/users/app/models/account.rb +++ /dev/null @@ -1,68 +0,0 @@ -# -# 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 - - attr_reader :user - - def initialize(user = nil) - @user = user - end - - # Returns the user record so it can be used in views. - def self.create(attrs) - @user = User.create(attrs).tap do |user| - Identity.create_for user - end - end - - def update(attrs) - if attrs[:password_verifier].present? - update_login(attrs[:login]) - @user.update_attributes attrs.slice(:password_verifier, :password_salt) - end - # TODO: move into identity controller - 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.disable_all_for(@user) - @user.destroy - end - - protected - - def update_login(login) - return unless login.present? - @old_identity = Identity.for(@user) - @user.login = login - @new_identity = Identity.for(@user) # based on the new login - @old_identity.destination = @user.email_address # alias old -> new - end - - def update_pgp_key(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/email.rb b/users/app/models/email.rb deleted file mode 100644 index a9a503f..0000000 --- a/users/app/models/email.rb +++ /dev/null @@ -1,26 +0,0 @@ -class Email < String - include ActiveModel::Validations - - validates :email, - :format => { - :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/, #local part of email is case-sensitive, so allow uppercase letter. - :message => "needs to be a valid email address" - } - - def to_partial_path - "emails/email" - end - - def to_param - to_s - end - - def email - self - end - - def handle - self.split('@').first - end - -end diff --git a/users/app/models/identity.rb b/users/app/models/identity.rb deleted file mode 100644 index 9b97b51..0000000 --- a/users/app/models/identity.rb +++ /dev/null @@ -1,136 +0,0 @@ -class Identity < CouchRest::Model::Base - include LoginFormatValidation - - use_database :identities - - belongs_to :user - - property :address, LocalEmail - property :destination, Email - property :keys, HashWithIndifferentAccess - - validate :unique_forward - validate :alias_available - validate :address_local_email - validate :destination_email - - design do - view :by_user_id - view :by_address_and_destination - view :by_address - view :pgp_key_by_email, - map: <<-EOJS - function(doc) { - if (doc.type != 'Identity') { - return; - } - if (typeof doc.keys === "object") { - 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 - - def self.for(user, attributes = {}) - find_for(user, attributes) || build_for(user, attributes) - end - - def self.find_for(user, attributes = {}) - attributes.reverse_merge! attributes_from_user(user) - find_by_address_and_destination [attributes[:address], attributes[:destination]] - end - - def self.build_for(user, attributes = {}) - attributes.reverse_merge! attributes_from_user(user) - Identity.new(attributes) - end - - def self.create_for(user, attributes = {}) - identity = build_for(user, attributes) - identity.save - 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, - destination: user.email_address - } - 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, key) - return if keys[type] == key.to_s - write_attribute('keys', keys.merge(type => key.to_s)) - end - - # for LoginFormatValidation - def login - self.address.handle - end - - protected - - def unique_forward - same = Identity.find_by_address_and_destination([address, destination]) - if same && same != self - errors.add :base, "This alias already exists" - end - end - - def alias_available - same = Identity.find_by_address(address) - if same && same.user != self.user - errors.add :base, "This email has already been taken" - end - end - - def address_local_email - return if address.valid? #this ensures it is LocalEmail - self.errors.add(:address, address.errors.messages[:email].first) #assumes only one error - end - - def destination_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 - -end diff --git a/users/app/models/local_email.rb b/users/app/models/local_email.rb deleted file mode 100644 index 2b4c65e..0000000 --- a/users/app/models/local_email.rb +++ /dev/null @@ -1,68 +0,0 @@ -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] - end - - validates :email, - :format => { - :with => /@#{domain}\Z/i, - :message => "needs to end in @#{domain}" - } - - validate :handle_allowed - - def initialize(s) - super - append_domain_if_needed - end - - def to_key - [handle] - end - - def domain - LocalEmail.domain - end - - protected - - def append_domain_if_needed - unless self.index('@') - self << '@' + domain - 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/login_format_validation.rb b/users/app/models/login_format_validation.rb deleted file mode 100644 index c1fcf70..0000000 --- a/users/app/models/login_format_validation.rb +++ /dev/null @@ -1,21 +0,0 @@ -module LoginFormatValidation - extend ActiveSupport::Concern - - #TODO: Probably will replace this. Playing with using it for aliases too, but won't want it connected to login field. - - included do - # Have multiple regular expression validations so we can get specific error messages: - validates :login, - :format => { :with => /\A.{2,}\z/, - :message => "Must have at least two characters"} - validates :login, - :format => { :with => /\A[a-z\d_\.-]+\z/, - :message => "Only lowercase letters, digits, . - and _ allowed."} - validates :login, - :format => { :with => /\A[a-z].*\z/, - :message => "Must begin with a lowercase letter"} - validates :login, - :format => { :with => /\A.*[a-z\d]\z/, - :message => "Must end with a letter or digit"} - end -end diff --git a/users/app/models/message.rb b/users/app/models/message.rb deleted file mode 100644 index 424f094..0000000 --- a/users/app/models/message.rb +++ /dev/null @@ -1,29 +0,0 @@ -class Message < CouchRest::Model::Base - - use_database :messages - - property :text, String - property :user_ids_to_show, [String] - property :user_ids_have_shown, [String] # is this necessary to store? - - timestamps! - - design do - own_path = Pathname.new(File.dirname(__FILE__)) - load_views(own_path.join('..', 'designs', 'message')) - end - - def mark_as_read_by(user) - user_ids_to_show.delete(user.id) - # is it necessary to keep track of what users have already seen it? - user_ids_have_shown << user.id unless read_by?(user) - end - - def read_by?(user) - user_ids_have_shown.include?(user.id) - end - - def unread_by?(user) - user_ids_to_show.include?(user.id) - end -end diff --git a/users/app/models/pgp_key.rb b/users/app/models/pgp_key.rb deleted file mode 100644 index 66f8660..0000000 --- a/users/app/models/pgp_key.rb +++ /dev/null @@ -1,48 +0,0 @@ -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 deleted file mode 100644 index 299aaf1..0000000 --- a/users/app/models/service_level.rb +++ /dev/null @@ -1,19 +0,0 @@ -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/session.rb b/users/app/models/session.rb deleted file mode 100644 index 0d7e10e..0000000 --- a/users/app/models/session.rb +++ /dev/null @@ -1,32 +0,0 @@ -class Session < SRP::Session - include ActiveModel::Validations - include LoginFormatValidation - - attr_accessor :login - - validates :login, :presence => true - - def initialize(user = nil, aa = nil) - super(user, aa) if user - end - - def persisted? - false - end - - def new_record? - true - end - - def to_model - self - end - - def to_key - [object_id] - end - - def to_param - nil - end -end diff --git a/users/app/models/token.rb b/users/app/models/token.rb deleted file mode 100644 index 4856c31..0000000 --- a/users/app/models/token.rb +++ /dev/null @@ -1,69 +0,0 @@ -class Token < CouchRest::Model::Base - - use_database :tokens - - belongs_to :user - - # timestamps! does not create setters and only sets updated_at - # if the object has changed and been saved. Instead of triggering - # that we rather use our own property we have control over: - property :last_seen_at, Time, accessible: false - - 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 - return nil - else - touch - return user - end - end - - # Tokens can be cleaned up in different ways. - # So let's make sure we don't crash if they disappeared - def destroy_with_rescue - destroy_without_rescue - rescue RestClient::ResourceNotFound - end - alias_method_chain :destroy, :rescue - - def touch - self.last_seen_at = Time.now - save - end - - def expired? - Token.expires_after and - last_seen_at < Token.expires_after.minutes.ago - end - - def initialize(*args) - super - 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 deleted file mode 100644 index 0fc17d2..0000000 --- a/users/app/models/unauthenticated_user.rb +++ /dev/null @@ -1,6 +0,0 @@ -# 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 deleted file mode 100644 index c297ac8..0000000 --- a/users/app/models/user.rb +++ /dev/null @@ -1,179 +0,0 @@ -class User < CouchRest::Model::Base - include LoginFormatValidation - - use_database :users - - property :login, String, :accessible => true - property :password_verifier, String, :accessible => true - property :password_salt, String, :accessible => true - - 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 - - property :one_month_warning_sent, TrueClass - - before_save :update_effective_service_level - - validates :login, :password_salt, :password_verifier, - :presence => true - - validates :login, - :uniqueness => true, - :if => :serverside? - - validate :login_is_unique_alias - - validates :password_salt, :password_verifier, - :format => { :with => /\A[\dA-Fa-f]+\z/, :message => "Only hex numbers allowed" } - - validates :password, :presence => true, - :confirmation => true, - :format => { :with => /.{8}.*/, :message => "needs to be at least 8 characters long" } - - timestamps! - - design do - own_path = Pathname.new(File.dirname(__FILE__)) - load_views(own_path.join('..', 'designs', 'user')) - view :by_login - view :by_created_at - end # end of design - - def to_json(options={}) - { - :login => login, - :ok => valid? - }.to_json(options) - end - - def salt - password_salt.hex - end - - def verifier - password_verifier.hex - end - - def username - login - end - - def email_address - LocalEmail.new(login) - end - - # Since we are storing admins by login, we cannot allow admins to change their login. - def is_admin? - APP_CONFIG['admins'].include? self.login - end - - def most_recent_tickets(count=3) - Ticket.for_user(self).limit(count).all #defaults to having most recent updated first - end - - def messages(unseen = true) - #TODO for now this only shows unseen messages. Will we ever want seen ones? Is it necessary to store? - - # we don't want to emit all the userids associated with a message, so only emit id and text. - Message.by_user_ids_to_show.key(self.id).map { |message| [message.id, message.text] } - - end - - # DEPRECATED - # - # Please set the key on the identity directly - # WARNING: This will not be serialized with the user record! - # It is only a workaround for the key form. - def public_key=(value) - identity.set_key(:pgp, value) - end - - # DEPRECATED - # - # Please access identity.keys[:pgp] directly - def public_key - identity.keys[:pgp] - end - - def account - Account.new(self) - end - - def identity - @identity ||= Identity.for(self) - end - - def refresh_identity - @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 - - - def self.send_one_month_warnings - - # To determine warnings to send, need to get all users where one_month_warning_sent is not set, and where it was created greater than or equal to 1 month ago. - # TODO: might want to further limit to enabled accounts, and, based on provider's service level configuration, for particular service levels. - users_to_warn = User.by_created_at_and_one_month_warning_not_sent.endkey(Time.now-1.month) - - users_to_warn.each do |user| - # instead of loop could use something like: - # message.user_ids_to_show = users_to_warn.map(&:id) - # but would still need to loop through users to store one_month_warning_sent - - if !@message - # create a message for today's date - # only want to create once, and only if it will be used. - @message = Message.new(:text => I18n.t(:payment_one_month_warning, :date_in_one_month => (Time.now+1.month).strftime("%Y-%d-%m"))) - end - - @message.user_ids_to_show << user.id - user.one_month_warning_sent = true - user.save - end - @message.save if @message - - end - - protected - - ## - # Validation Functions - ## - - def login_is_unique_alias - alias_identity = Identity.find_by_address(self.email_address) - return if alias_identity.blank? - if alias_identity.user != self - errors.add(:login, "has already been taken") - end - end - - def password - password_verifier - end - - # used as a condition for validations that are server side only - 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/.gitkeep b/users/app/views/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/app/views/emails/_email.html.haml b/users/app/views/emails/_email.html.haml deleted file mode 100644 index ea59cec..0000000 --- a/users/app/views/emails/_email.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -= wrapped(email, local_assigns) do - = email - - if local_assigns[:with].try(:include?, :delete) - = link_to(user_email_alias_path(@user, email), :method => :delete) do - %i.icon-remove - diff --git a/users/app/views/sessions/new.html.haml b/users/app/views/sessions/new.html.haml deleted file mode 100644 index 771dc97..0000000 --- a/users/app/views/sessions/new.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -.span1 -.span9 - = render :partial => 'users/warnings' - %h2=t :login - = simple_form_for [:api, @session], :validate => true, :html => { :id => :new_session, :class => 'form-horizontal' } do |f| - = f.input :login, :required => false, :label => t(:username), :input_html => { :id => :srp_username } - = f.input :password, :required => false, :input_html => { :id => :srp_password } - .form-actions - = f.button :submit, :value => t(:login), :class => 'btn-primary' - = link_to t(:cancel), home_path, :class => 'btn' diff --git a/users/app/views/sessions/new.json.erb b/users/app/views/sessions/new.json.erb deleted file mode 100644 index 36154b8..0000000 --- a/users/app/views/sessions/new.json.erb +++ /dev/null @@ -1,3 +0,0 @@ -{ -"errors": <%= raw @errors.to_json %> -} diff --git a/users/app/views/users/_change_password.html.haml b/users/app/views/users/_change_password.html.haml deleted file mode 100644 index 425e3ee..0000000 --- a/users/app/views/users/_change_password.html.haml +++ /dev/null @@ -1,21 +0,0 @@ --# --# 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 deleted file mode 100644 index e465125..0000000 --- a/users/app/views/users/_change_pgp_key.html.haml +++ /dev/null @@ -1,13 +0,0 @@ --# --# 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 deleted file mode 100644 index 61e67d9..0000000 --- a/users/app/views/users/_change_service_level.html.haml +++ /dev/null @@ -1,18 +0,0 @@ --# 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 deleted file mode 100644 index 445f3c4..0000000 --- a/users/app/views/users/_destroy_account.html.haml +++ /dev/null @@ -1,27 +0,0 @@ --# --# 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 deleted file mode 100644 index 1d2b68a..0000000 --- a/users/app/views/users/_edit.html.haml +++ /dev/null @@ -1,14 +0,0 @@ --# --# edit user form, used by both show and edit actions. --# --# 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 deleted file mode 100644 index 583d22f..0000000 --- a/users/app/views/users/_user.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -%tr - %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/_warnings.html.haml b/users/app/views/users/_warnings.html.haml deleted file mode 100644 index 79ab103..0000000 --- a/users/app/views/users/_warnings.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%noscript - %div.alert.alert-error=t :js_required_html -#cookie_warning.alert.alert-error{:style => "display:none"} - =t :cookie_disabled_warning -:javascript - document.cookie = "testing=cookies_enabled; path=/"; - if(document.cookie.indexOf("testing=cookies_enabled") < 0) - { - document.getElementById('cookie_warning').style.display = 'block'; - } else { - document.getElementById('cookie_warning').style.display = 'none'; - } \ No newline at end of file diff --git a/users/app/views/users/edit.html.haml b/users/app/views/users/edit.html.haml deleted file mode 100644 index 434c025..0000000 --- a/users/app/views/users/edit.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render 'edit' diff --git a/users/app/views/users/index.html.haml b/users/app/views/users/index.html.haml deleted file mode 100644 index fc1001e..0000000 --- a/users/app/views/users/index.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -- @show_navigation = false - -= form_tag users_path, :method => :get, :class => "form-search" do - .input-append - = text_field_tag :query, params[:query], :id => 'user-typeahead', :class => "search-query", :autocomplete => :off - %button.btn{:type => :submit}= t(:search) - -%table.table.table-striped - %tr - %th= t(:username) - %th= t(:created) - %th= t(:updated) - = render @users.all diff --git a/users/app/views/users/new.html.haml b/users/app/views/users/new.html.haml deleted file mode 100644 index aecf831..0000000 --- a/users/app/views/users/new.html.haml +++ /dev/null @@ -1,19 +0,0 @@ --# --# This form is handled entirely by javascript, so take care when changing element ids. --# - -- form_options = {:url => '/not-used', :html => {:id => 'new_user', :class => user_form_class('form-horizontal')}, :validate => true} - -.span1 -.span9 - = render :partial => 'warnings' - %h2=t :signup - = simple_form_for(@user, form_options) do |f| - %legend= t(:signup_message) - = 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, :validate => true, :input_html => { :id => :srp_password_confirmation } - .form-actions - = f.button :submit, :value => t(:signup), :class => 'btn btn-primary' - = link_to t(:cancel), home_path, :class => 'btn' - diff --git a/users/app/views/users/show.html.haml b/users/app/views/users/show.html.haml deleted file mode 100644 index c587017..0000000 --- a/users/app/views/users/show.html.haml +++ /dev/null @@ -1,25 +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), billing_top_link(@user)) if APP_CONFIG[:billing] - .container-fluid - .row-fluid - = home_page_buttons(true) \ No newline at end of file diff --git a/users/app/views/v1/sessions/new.json.erb b/users/app/views/v1/sessions/new.json.erb deleted file mode 100644 index 36154b8..0000000 --- a/users/app/views/v1/sessions/new.json.erb +++ /dev/null @@ -1,3 +0,0 @@ -{ -"errors": <%= raw @errors.to_json %> -} diff --git a/users/app/views/webfinger/host_meta.xml.erb b/users/app/views/webfinger/host_meta.xml.erb deleted file mode 100644 index cfcbcc0..0000000 --- a/users/app/views/webfinger/host_meta.xml.erb +++ /dev/null @@ -1,11 +0,0 @@ - - - - <%= @host_meta.subject %> - - <%- @host_meta.links.each do |rel, link| %> - - <%- end %> - diff --git a/users/app/views/webfinger/search.xml.erb b/users/app/views/webfinger/search.xml.erb deleted file mode 100644 index 7328552..0000000 --- a/users/app/views/webfinger/search.xml.erb +++ /dev/null @@ -1,7 +0,0 @@ - - - <%= @presenter.subject %> - <%- @presenter.links.each do |rel, link| %> - type=<%=link[:type]%> href="<%= link[:key] %>"/> - <% end %> - diff --git a/users/test/factories.rb b/users/test/factories.rb deleted file mode 100644 index ae00d43..0000000 --- a/users/test/factories.rb +++ /dev/null @@ -1,34 +0,0 @@ -FactoryGirl.define do - - factory :user do - login { Faker::Internet.user_name } - password_verifier "1234ABCD" - password_salt "4321AB" - - factory :user_with_settings do - email_forward { Faker::Internet.email } - email_aliases_attributes do - {:a => Faker::Internet.user_name + '@' + APP_CONFIG[:domain]} - end - end - - factory :admin_user do - after(:build) do |admin| - admin.stubs(:is_admin?).returns(true) - end - end - end - - factory :token do - user - end - - factory :pgp_key do - keyblock <<-EOPGP ------BEGIN PGP PUBLIC KEY BLOCK----- -+Dummy+PGP+KEY+++Dummy+PGP+KEY+++Dummy+PGP+KEY+++Dummy+PGP+KEY+ -#{SecureRandom.base64(4032)} ------END PGP PUBLIC KEY BLOCK----- - EOPGP - end -end diff --git a/users/test/fixtures/.gitkeep b/users/test/fixtures/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/test/functional/.gitkeep b/users/test/functional/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/test/functional/application_controller_test.rb b/users/test/functional/application_controller_test.rb deleted file mode 100644 index c4c922b..0000000 --- a/users/test/functional/application_controller_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'test_helper' - -class ApplicationControllerTest < ActionController::TestCase - - def setup - # so we can test the effect on the response - @controller.response = @response - end - - def test_require_login_redirect - @controller.send(:require_login) - assert_access_denied(true, false) - end - - def test_require_login - login - @controller.send(:require_login) - assert_access_denied(false) - end - - def test_require_admin - login - @current_user.expects(:is_admin?).returns(false) - @controller.send(:require_admin) - assert_access_denied - end - -end diff --git a/users/test/functional/helper_methods_test.rb b/users/test/functional/helper_methods_test.rb deleted file mode 100644 index 44226ae..0000000 --- a/users/test/functional/helper_methods_test.rb +++ /dev/null @@ -1,39 +0,0 @@ -# -# Testing and documenting the helper methods available from -# ApplicationController -# - -require 'test_helper' - -class HelperMethodsTest < ActionController::TestCase - tests ApplicationController - - # we test them right in here... - include ApplicationController._helpers - - # the helpers all reference the controller. - def controller - @controller - end - - def test_current_user - login - assert_equal @current_user, current_user - end - - def test_logged_in - login - assert logged_in? - end - - def test_logged_out - assert !logged_in? - end - - def test_admin - login - @current_user.expects(:is_admin?).returns(bool = stub) - assert_equal bool, admin? - end - -end diff --git a/users/test/functional/keys_controller_test.rb b/users/test/functional/keys_controller_test.rb deleted file mode 100644 index 863be93..0000000 --- a/users/test/functional/keys_controller_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'test_helper' - -class KeysControllerTest < ActionController::TestCase - - test "get existing public key" do - public_key = 'my public key' - @user = stub_record :user, :public_key => public_key - User.stubs(:find_by_login).with(@user.login).returns(@user) - get :show, :login => @user.login - assert_response :success - assert_equal "text/text", response.content_type - assert_equal public_key, response.body - end - - test "get non-existing public key for user" do - # this isn't a scenerio that should generally occur. - @user = stub_record :user - User.stubs(:find_by_login).with(@user.login).returns(@user) - get :show, :login => @user.login - assert_response :success - assert_equal "text/text", response.content_type - assert_equal '', response.body.strip - end - - test "get public key for non-existing user" do - # raise 404 error if user doesn't exist (doesn't need to be this routing error, but seems fine to assume for now): - assert_raise(ActionController::RoutingError) { - get :show, :login => 'asdkljslksjfdlskfj' - } - end - -end diff --git a/users/test/functional/sessions_controller_test.rb b/users/test/functional/sessions_controller_test.rb deleted file mode 100644 index fe7903f..0000000 --- a/users/test/functional/sessions_controller_test.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'test_helper' - -# This is a simple controller unit test. -# We're stubbing out both warden and srp. -# There's an integration test testing the full rack stack and srp -class SessionsControllerTest < ActionController::TestCase - - setup do - @user = stub :login => "me", :id => 123 - @client_hex = 'a123' - end - - test "should get login screen" do - get :new - assert_response :success - assert_equal "text/html", response.content_type - assert_template "sessions/new" - end - - test "redirect to home_url if logged in" do - login - get :new - assert_response :redirect - assert_redirected_to home_url - end - - test "renders json" do - get :new, :format => :json - assert_response :success - assert_json_error nil - end - - test "renders warden errors" do - request.env['warden.options'] = {attempted_path: '/1/sessions/asdf.json'} - strategy = stub :message => {:field => :translate_me} - request.env['warden'].stubs(:winning_strategy).returns(strategy) - I18n.expects(:t).with(:translate_me).at_least_once.returns("translation stub") - get :new, :format => :json - assert_response 422 - assert_json_error :field => "translation stub" - end - - test "renders failed attempt message" do - request.env['warden.options'] = {attempted_path: '/1/sessions/asdf.json'} - request.env['warden'].stubs(:winning_strategy).returns(nil) - get :new, :format => :json - assert_response 422 - assert_json_error :login => I18n.t(:all_strategies_failed) - end - - test "destory should logout" do - login - expect_logout - delete :destroy - assert_response :redirect - assert_redirected_to home_url - end - -end diff --git a/users/test/functional/test_helpers_test.rb b/users/test/functional/test_helpers_test.rb deleted file mode 100644 index 845e516..0000000 --- a/users/test/functional/test_helpers_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -# -# There are a few test helpers for dealing with login etc. -# We test them here and also document their behaviour. -# - -require 'test_helper' - -class TestHelpersTest < ActionController::TestCase - tests ApplicationController # testing no controller in particular - - def test_login_stubs_warden - login - assert_equal @current_user, request.env['warden'].user - end - - def test_login_token_authenticates - login - assert_equal @current_user, @controller.send(:token_authenticate) - end - - def test_login_stubs_token - login - assert @token - assert_equal @current_user, @token.authenticate - end - - def test_login_adds_token_header - login - token_present = @controller.authenticate_with_http_token do |token, options| - assert_equal @token.id, token - end - # authenticate_with_http_token just returns nil and does not - # execute the block if there is no token. So we have to also - # ensure it was run: - assert token_present - end -end - diff --git a/users/test/functional/users_controller_test.rb b/users/test/functional/users_controller_test.rb deleted file mode 100644 index 0713836..0000000 --- a/users/test/functional/users_controller_test.rb +++ /dev/null @@ -1,165 +0,0 @@ -require 'test_helper' - -class UsersControllerTest < ActionController::TestCase - - test "should get new" do - get :new - assert_equal User, assigns(:user).class - assert_response :success - end - - test "new should redirect logged in users" do - login - get :new - assert_response :redirect - assert_redirected_to home_path - end - - test "failed show without login" do - user = find_record :user - get :show, :id => user.id - assert_response :redirect - assert_redirected_to login_path - end - - test "user can see user" do - user = find_record :user, - :most_recent_tickets => [] - login user - get :show, :id => user.id - assert_response :success - end - - test "admin can see other user" do - user = find_record :user, - :most_recent_tickets => [] - login :is_admin? => true - get :show, :id => user.id - assert_response :success - - end - - test "user cannot see other user" do - user = find_record :user, - :most_recent_tickets => [] - login - get :show, :id => user.id - assert_response :redirect - assert_access_denied - end - - test "may not show non-existing user without auth" do - nonid = 'thisisnotanexistinguserid' - - get :show, :id => nonid - assert_access_denied(true, false) - end - - test "may not show non-existing user without admin" do - nonid = 'thisisnotanexistinguserid' - login - - get :show, :id => nonid - assert_access_denied - end - - test "redirect admin to user list for non-existing user" do - nonid = 'thisisnotanexistinguserid' - login :is_admin? => true - get :show, :id => nonid - assert_response :redirect - assert_equal({:alert => "No such user."}, flash.to_hash) - assert_redirected_to users_path - end - - test "should get edit view" do - user = find_record :user - - login user - get :edit, :id => user.id - - assert_equal user, assigns[:user] - end - - test "admin can destroy user" do - user = find_record :user - - # we destroy the user record and the associated data... - user.expects(:destroy) - Identity.expects(:disable_all_for).with(user) - Ticket.expects(:destroy_all_from).with(user) - - login :is_admin? => true - delete :destroy, :id => user.id - - assert_response :redirect - assert_redirected_to users_path - end - - test "user can cancel account" do - user = find_record :user - - # we destroy the user record and the associated data... - user.expects(:destroy) - Identity.expects(:disable_all_for).with(user) - Ticket.expects(:destroy_all_from).with(user) - - login user - expect_logout - delete :destroy, :id => @current_user.id - - assert_response :redirect - assert_redirected_to bye_url - end - - test "non-admin can't destroy user" do - user = find_record :user - - login - delete :destroy, :id => user.id - - assert_access_denied - end - - test "admin can list users" do - login :is_admin? => true - get :index - - assert_response :success - assert assigns(:users) - end - - test "non-admin can't list users" do - login - get :index - - assert_access_denied - end - - test "admin can search users" do - login :is_admin? => true - get :index, :query => "a" - - assert_response :success - assert assigns(:users) - end - - test "user cannot enable own account" do - user = find_record :user - login - post :enable, :id => user.id - assert_access_denied - end - - test "admin can deactivate user" do - user = find_record :user - assert user.enabled? - user.expects(:save).returns(true) - - login :is_admin? => true - - post :deactivate, :id => user.id - assert !assigns(:user).enabled? - end - -end diff --git a/users/test/functional/v1/messages_controller_test.rb b/users/test/functional/v1/messages_controller_test.rb deleted file mode 100644 index 24a5b1f..0000000 --- a/users/test/functional/v1/messages_controller_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'test_helper' - -class V1::MessagesControllerTest < ActionController::TestCase - - setup do - @user = FactoryGirl.build(:user) - @user.save - @message = Message.new(:text => 'a test message') - @message.user_ids_to_show << @user.id - @message.save - end - - teardown do - @message.destroy - @user.destroy - end - - test "get messages for user" do - login @user - get :index - assert response.body.include? @message.text - assert response.body.include? @message.id - end - - test "mark message read for user" do - login @user - assert @message.user_ids_to_show.include?(@user.id) - assert !@message.user_ids_have_shown.include?(@user.id) - put :update, :id => @message.id - @message.reload - assert !@message.user_ids_to_show.include?(@user.id) - assert @message.user_ids_have_shown.include?(@user.id) - assert_json_response true - end - - test "do not get seen messages" do - login @user - put :update, :id => @message.id - @message.reload - get :index - assert !(response.body.include? @message.text) - assert !(response.body.include? @message.id) - end - - - test "mark read responds even with bad inputs" do - login @user - put :update, :id => 'more nonsense' - assert_json_response false - end - - test "fails if not authenticated" do - get :index, :format => :json - assert_access_denied - end - -end diff --git a/users/test/functional/v1/sessions_controller_test.rb b/users/test/functional/v1/sessions_controller_test.rb deleted file mode 100644 index df0d681..0000000 --- a/users/test/functional/v1/sessions_controller_test.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'test_helper' - -# This is a simple controller unit test. -# We're stubbing out both warden and srp. -# There's an integration test testing the full rack stack and srp -class V1::SessionsControllerTest < ActionController::TestCase - - setup do - @request.env['HTTP_HOST'] = 'api.lvh.me' - @user = stub_record :user, {}, true - @client_hex = 'a123' - end - - test "renders json" do - get :new, :format => :json - assert_response :success - assert_json_error nil - end - - test "renders warden errors" do - request.env['warden.options'] = {attempted_path: 'path/to/controller'} - strategy = stub :message => {:field => :translate_me} - request.env['warden'].stubs(:winning_strategy).returns(strategy) - I18n.expects(:t).with(:translate_me).at_least_once.returns("translation stub") - get :new, :format => :json - assert_response 422 - assert_json_error :field => "translation stub" - end - - # Warden takes care of parsing the params and - # rendering the response. So not much to test here. - test "should perform handshake" do - request.env['warden'].expects(:authenticate!) - # make sure we don't get a template missing error: - @controller.stubs(:render) - post :create, :login => @user.login, 'A' => @client_hex - end - - test "should authenticate" do - request.env['warden'].expects(:authenticate!) - @controller.stubs(:current_user).returns(@user) - handshake = stub(:to_hash => {h: "ash"}) - session[:handshake] = handshake - - post :update, :id => @user.login, :client_auth => @client_hex - - assert_nil session[:handshake] - assert_response :success - assert json_response.keys.include?("id") - assert json_response.keys.include?("token") - assert token = Token.find(json_response['token']) - assert_equal @user.id, token.user_id - end - - test "destroy should logout" do - login - expect_logout - delete :destroy - assert_response 204 - end - -end diff --git a/users/test/functional/v1/users_controller_test.rb b/users/test/functional/v1/users_controller_test.rb deleted file mode 100644 index 7cd9b0c..0000000 --- a/users/test/functional/v1/users_controller_test.rb +++ /dev/null @@ -1,74 +0,0 @@ -require 'test_helper' - -class V1::UsersControllerTest < ActionController::TestCase - - test "user can change settings" do - user = find_record :user - changed_attribs = record_attributes_for :user_with_settings - account_settings = stub - account_settings.expects(:update).with(changed_attribs) - Account.expects(:new).with(user).returns(account_settings) - - login user - put :update, :user => changed_attribs, :id => user.id, :format => :json - - assert_equal user, assigns[:user] - assert_response 204 - assert_equal " ", @response.body - end - - test "admin can update user" do - user = find_record :user - changed_attribs = record_attributes_for :user_with_settings - account_settings = stub - account_settings.expects(:update).with(changed_attribs) - Account.expects(:new).with(user).returns(account_settings) - - login :is_admin? => true - put :update, :user => changed_attribs, :id => user.id, :format => :json - - assert_equal user, assigns[:user] - assert_response 204 - end - - test "user cannot update other user" do - user = find_record :user - login - put :update, :user => record_attributes_for(:user_with_settings), :id => user.id, :format => :json - assert_access_denied - end - - test "should create new user" do - user_attribs = record_attributes_for :user - user = User.new(user_attribs) - Account.expects(:create).with(user_attribs).returns(user) - - post :create, :user => user_attribs, :format => :json - - assert_nil session[:user_id] - assert_json_response user - assert_response :success - end - - test "should redirect to signup form on failed attempt" do - user_attribs = record_attributes_for :user - user_attribs.slice!('login') - user = User.new(user_attribs) - assert !user.valid? - Account.expects(:create).with(user_attribs).returns(user) - - post :create, :user => user_attribs, :format => :json - - assert_json_error user.errors.messages - assert_response 422 - end - - test "admin can autocomplete users" do - login :is_admin? => true - get :index, :query => 'a', :format => :json - - assert_response :success - assert assigns(:users) - end - -end diff --git a/users/test/functional/webfinger_controller_test.rb b/users/test/functional/webfinger_controller_test.rb deleted file mode 100644 index 6597b69..0000000 --- a/users/test/functional/webfinger_controller_test.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'test_helper' - -class WebfingerControllerTest < ActionController::TestCase - - test "get host meta xml" do - get :host_meta, :format => :xml - assert_response :success - assert_equal "application/xml", response.content_type - end - - test "get host meta json" do - get :host_meta, :format => :json - assert_response :success - assert_equal "application/json", response.content_type - end - - test "get user webfinger xml" do - @user = stub_record :user, :public_key => 'my public key' - User.stubs(:find_by_login).with(@user.login).returns(@user) - get :search, :q => @user.email_address.to_s, :format => :xml - assert_response :success - assert_equal "application/xml", response.content_type - end - - test "get user webfinger json" do - @user = stub_record :user, :public_key => 'my public key' - User.stubs(:find_by_login).with(@user.login).returns(@user) - get :search, :q => @user.email_address.to_s, :format => :json - assert_response :success - assert_equal "application/json", response.content_type - end - -end diff --git a/users/test/integration/.gitkeep b/users/test/integration/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/users/test/integration/api/Readme.md b/users/test/integration/api/Readme.md deleted file mode 100644 index 04363bd..0000000 --- a/users/test/integration/api/Readme.md +++ /dev/null @@ -1,23 +0,0 @@ -API tests -========== - - -Testing the restful api from a simple python client as that's what we'll be using. - -This test so far mostly demoes the API. We have no SRP calc in there. - -TODO: keep track of the cookies during login. The server uses the session to keep track of the random numbers A and B. - -The output of signup_and_login_wrong_password pretty well describes the SRP API: - -``` -POST: http://localhost:9292/users.json - {"user[password_salt]": "54321", "user[password_verifier]": "12345", "user[login]": "SWQ055"} - -> {"password_salt":"54321","login":"SWQ055"} -POST: http://localhost:9292/sessions - {"A": "12345", "login": "SWQ055"} - -> {"B":"1778367531e93a4c7713c76f67649f35a4211ebc520926ae8c3848cd66171651"} -PUT: http://localhost:9292/sessions/SWQ055 - {"M": "123ABC"} - -> {"errors":[{"login":"Not a valid username/password combination"},{"password":"Not a valid username/password combination"}]} -``` diff --git a/users/test/integration/api/login_test.rb b/users/test/integration/api/login_test.rb deleted file mode 100644 index 92d153f..0000000 --- a/users/test/integration/api/login_test.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'test_helper' -require_relative 'srp_test' - -class LoginTest < SrpTest - - setup do - register_user - end - - test "requires handshake before validation" do - validate("bla") - assert_json_error login: I18n.t(:all_strategies_failed) - end - - test "login with srp" do - authenticate - assert_equal ["M2", "id", "token"], server_auth.keys - assert last_response.successful? - assert_nil server_auth["errors"] - assert server_auth["M2"] - end - - test "wrong password login attempt" do - authenticate password: "wrong password" - assert_json_error "base" => "Not a valid username/password combination" - assert !last_response.successful? - assert_nil server_auth["M2"] - end - - test "wrong username login attempt" do - assert_raises RECORD_NOT_FOUND do - authenticate login: "wrong login" - end - assert_json_error "base" => "Not a valid username/password combination" - assert !last_response.successful? - assert_nil server_auth - end - - test "logout" do - authenticate - logout - assert_equal 204, last_response.status - end - - test "logout requires token" do - authenticate - logout(nil, {}) - assert_equal 422, last_response.status - end -end diff --git a/users/test/integration/api/pgp_key_test.rb b/users/test/integration/api/pgp_key_test.rb deleted file mode 100644 index 4c7fb4c..0000000 --- a/users/test/integration/api/pgp_key_test.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'test_helper' -require_relative 'srp_test' - -class PgpKeyTest < SrpTest - - setup do - # todo: prepare user and login without doing the srp dance - register_user - authenticate - end - - test "upload pgp key" do - update_user public_key: key - assert_equal key, Identity.for(@user).keys[:pgp] - end - - # eventually probably want to remove most of this into a non-integration - # functional test - test "prevent uploading invalid key" do - update_user public_key: "invalid key" - assert_nil Identity.for(@user).keys[:pgp] - end - - test "prevent emptying public key" do - update_user public_key: key - update_user public_key: "" - assert_equal key, Identity.for(@user).keys[:pgp] - end - - protected - - def key - @key ||= FactoryGirl.build :pgp_key - end -end diff --git a/users/test/integration/api/python/flow_with_srp.py b/users/test/integration/api/python/flow_with_srp.py deleted file mode 100755 index 9fc168b..0000000 --- a/users/test/integration/api/python/flow_with_srp.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python - -# under development - -import requests -import json -import string -import random -import srp._pysrp as srp -import binascii - -safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x) - -# using globals for now -# server = 'https://dev.bitmask.net/1' -server = 'http://api.lvh.me:3000/1' - -def run_tests(): - login = 'test_' + id_generator() - password = id_generator() + id_generator() - usr = srp.User( login, password, srp.SHA256, srp.NG_1024 ) - print_and_parse(signup(login, password)) - - auth = print_and_parse(authenticate(usr)) - verify_or_debug(auth, usr) - assert usr.authenticated() - - usr = change_password(auth['id'], login, auth['token']) - - auth = print_and_parse(authenticate(usr)) - verify_or_debug(auth, usr) - # At this point the authentication process is complete. - assert usr.authenticated() - -# let's have some random name -def id_generator(size=6, chars=string.ascii_lowercase + string.digits): - return ''.join(random.choice(chars) for x in range(size)) - -# log the server communication -def print_and_parse(response): - request = response.request - print request.method + ': ' + response.url - if hasattr(request, 'data'): - print " " + json.dumps(response.request.data) - print " -> " + response.text - try: - return json.loads(response.text) - except ValueError: - return None - -def signup(login, password): - salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 ) - user_params = { - 'user[login]': login, - 'user[password_verifier]': binascii.hexlify(vkey), - 'user[password_salt]': binascii.hexlify(salt) - } - return requests.post(server + '/users.json', data = user_params, verify = False) - -def change_password(user_id, login, token): - password = id_generator() + id_generator() - salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 ) - user_params = { - 'user[password_verifier]': binascii.hexlify(vkey), - 'user[password_salt]': binascii.hexlify(salt) - } - auth_headers = { 'Authorization': 'Token token="' + token + '"'} - print user_params - print_and_parse(requests.put(server + '/users/' + user_id + '.json', data = user_params, verify = False, headers = auth_headers)) - return srp.User( login, password, srp.SHA256, srp.NG_1024 ) - - -def authenticate(usr): - session = requests.session() - uname, A = usr.start_authentication() - params = { - 'login': uname, - 'A': binascii.hexlify(A) - } - init = print_and_parse(session.post(server + '/sessions', data = params, verify=False)) - M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) ) - return session.put(server + '/sessions/' + uname, verify = False, - data = {'client_auth': binascii.hexlify(M)}) - -def verify_or_debug(auth, usr): - if ( 'errors' in auth ): - print ' u = "%x"' % usr.u - print ' x = "%x"' % usr.x - print ' v = "%x"' % usr.v - print ' S = "%x"' % usr.S - print ' K = "' + binascii.hexlify(usr.K) + '"' - print ' M = "' + binascii.hexlify(usr.M) + '"' - else: - usr.verify_session( safe_unhexlify(auth["M2"]) ) - -run_tests() diff --git a/users/test/integration/api/python/login_wrong_username.py b/users/test/integration/api/python/login_wrong_username.py deleted file mode 100755 index 390f250..0000000 --- a/users/test/integration/api/python/login_wrong_username.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python - -server = 'http://localhost:3000' - -import requests -import json -import string -import random - -def id_generator(size=6, chars=string.ascii_uppercase + string.digits): - return ''.join(random.choice(chars) for x in range(size)) - -params = { - 'login': 'python_test_user_'+id_generator(), - 'A': '12345', - } -r = requests.post(server + '/sessions', data = params) -print r.url -print r.text diff --git a/users/test/integration/api/python/signup.py b/users/test/integration/api/python/signup.py deleted file mode 100755 index 0d3a4e0..0000000 --- a/users/test/integration/api/python/signup.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python - -server = 'http://localhost:3000' - -import requests -import json -import string -import random - -def id_generator(size=6, chars=string.ascii_uppercase + string.digits): - return ''.join(random.choice(chars) for x in range(size)) - -user_params = { - 'user[login]': 'python_test_user_'+id_generator(), - 'user[password_verifier]': '12345', - 'user[password_salt]': '54321' - } -r = requests.post(server + '/users.json', data = user_params) -print r.url -print r.text diff --git a/users/test/integration/api/python/signup_and_login.py b/users/test/integration/api/python/signup_and_login.py deleted file mode 100755 index ac611d7..0000000 --- a/users/test/integration/api/python/signup_and_login.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python - -# FAILS -# -# This test is currently failing for me because the session is not kept. -# Played with it a bunch - is probably messed up right now as well. - - -server = 'http://localhost:3000' - -import requests -import json -import string -import random - -def id_generator(size=6, chars=string.ascii_uppercase + string.digits): - return ''.join(random.choice(chars) for x in range(size)) - -def print_and_parse(response): - print response.request.method + ': ' + response.url - print " " + json.dumps(response.request.data) - print " -> " + response.text - return json.loads(response.text) - -def signup(session): - user_params = { - 'user[login]': id_generator(), - 'user[password_verifier]': '12345', - 'user[password_salt]': 'AB54321' - } - return session.post(server + '/users.json', data = user_params) - -def authenticate(session, login): - params = { - 'login': login, - 'A': '12345', - } - init = print_and_parse(session.post(server + '/sessions', data = params)) - return session.put(server + '/sessions/' + login, data = {'client_auth': '123'}) - -session = requests.session() -user = print_and_parse(signup(session)) -# SRP signup would happen here and calculate M hex -auth = print_and_parse(authenticate(session, user['login'])) diff --git a/users/test/integration/api/python/signup_and_login_wrong_password.py b/users/test/integration/api/python/signup_and_login_wrong_password.py deleted file mode 100755 index 9efffa1..0000000 --- a/users/test/integration/api/python/signup_and_login_wrong_password.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python - -server = 'http://localhost:9292' - -import requests -import json -import string -import random - -def id_generator(size=6, chars=string.ascii_uppercase + string.digits): - return ''.join(random.choice(chars) for x in range(size)) - -def print_and_parse(response): - print response.request.method + ': ' + response.url - print " " + json.dumps(response.request.data) - print " -> " + response.text -# print " () " + json.dumps(requests.utils.dict_from_cookiejar(response.cookies)) - return json.loads(response.text) - -def signup(): - user_params = { - 'user[login]': id_generator(), - 'user[password_verifier]': '12345', - 'user[password_salt]': '54321' - } - return requests.post(server + '/users.json', data = user_params) - -def handshake(login): - params = { - 'login': login, - 'A': '12345', - } - return requests.post(server + '/sessions', data = params) - -def authenticate(login, M): - return requests.put(server + '/sessions/' + login, data = {'M': M}) - - -user = print_and_parse(signup()) -handshake = print_and_parse(handshake(user['login'])) -# SRP signup would happen here and calculate M hex -M = '123ABC' -auth = print_and_parse(authenticate(user['login'], M)) diff --git a/users/test/integration/api/python/umlauts.py b/users/test/integration/api/python/umlauts.py deleted file mode 100755 index 96fecbf..0000000 --- a/users/test/integration/api/python/umlauts.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -# under development - -import requests -import json -import string -import random -import srp._pysrp as srp -import binascii - -safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x) - -# using globals for now -# server = 'https://dev.bitmask.net/1' -server = 'http://api.lvh.me:3000/1' - -def run_tests(): - login = 'test_' + id_generator() - password = id_generator() + "äöì" + id_generator() - usr = srp.User( login, password, srp.SHA256, srp.NG_1024 ) - print_and_parse(signup(login, password)) - - auth = print_and_parse(authenticate(usr)) - verify_or_debug(auth, usr) - assert usr.authenticated() - - -# let's have some random name -def id_generator(size=6, chars=string.ascii_lowercase + string.digits): - return ''.join(random.choice(chars) for x in range(size)) - -# log the server communication -def print_and_parse(response): - request = response.request - print request.method + ': ' + response.url - if hasattr(request, 'data'): - print " " + json.dumps(response.request.data) - print " -> " + response.text - try: - return json.loads(response.text) - except ValueError: - return None - -def signup(login, password): - salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 ) - user_params = { - 'user[login]': login, - 'user[password_verifier]': binascii.hexlify(vkey), - 'user[password_salt]': binascii.hexlify(salt) - } - print json.dumps(user_params) - return requests.post(server + '/users.json', data = user_params, verify = False) - -def authenticate(usr): - session = requests.session() - uname, A = usr.start_authentication() - params = { - 'login': uname, - 'A': binascii.hexlify(A) - } - init = print_and_parse(session.post(server + '/sessions', data = params, verify=False)) - M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) ) - return session.put(server + '/sessions/' + uname, verify = False, - data = {'client_auth': binascii.hexlify(M)}) - -def verify_or_debug(auth, usr): - if ( 'errors' in auth ): - print ' u = "%x"' % usr.u - print ' x = "%x"' % usr.x - print ' v = "%x"' % usr.v - print ' S = "%x"' % usr.S - print ' K = "' + binascii.hexlify(usr.K) + '"' - print ' M = "' + binascii.hexlify(usr.M) + '"' - else: - usr.verify_session( safe_unhexlify(auth["M2"]) ) - -run_tests() diff --git a/users/test/integration/api/signup_test.rb b/users/test/integration/api/signup_test.rb deleted file mode 100644 index 236c547..0000000 --- a/users/test/integration/api/signup_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'test_helper' -require_relative 'srp_test' - -class SignupTest < SrpTest - - setup do - register_user - end - - test "signup response" do - assert_json_response :login => @login, :ok => true - assert last_response.successful? - end - - test "signup creates user" do - assert @user - assert_equal @login, @user.login - end -end - diff --git a/users/test/integration/api/srp_test.rb b/users/test/integration/api/srp_test.rb deleted file mode 100644 index 946450e..0000000 --- a/users/test/integration/api/srp_test.rb +++ /dev/null @@ -1,104 +0,0 @@ -class SrpTest < RackTest - - teardown do - if @user - cleanup_user - end - Warden.test_reset! - end - - # this test wraps the api and implements the interface the ruby-srp client. - def handshake(login, aa) - post "http://api.lvh.me:3000/1/sessions.json", - :login => login, - 'A' => aa, - :format => :json - response = JSON.parse(last_response.body) - if response['errors'] - raise RECORD_NOT_FOUND.new(response['errors']) - else - return response['B'] - end - end - - def validate(m) - put "http://api.lvh.me:3000/1/sessions/" + @login + '.json', - :client_auth => m, - :format => :json - return JSON.parse(last_response.body) - end - - protected - - attr_reader :server_auth - - def register_user(login = "integration_test_user", password = 'srp, verify me!') - cleanup_user(login) - post 'http://api.lvh.me:3000/1/users.json', - user_params(login: login, password: password) - @user = User.find_by_login(login) - @login = login - @password = password - end - - def update_user(params) - put "http://api.lvh.me:3000/1/users/" + @user.id + '.json', - user_params(params), - auth_headers - end - - def authenticate(params = nil) - @server_auth = srp(params).authenticate(self) - end - - def auth_headers - return {} if @server_auth.nil? - { - "HTTP_AUTHORIZATION" => encoded_token - } - end - - def encoded_token - ActionController::HttpAuthentication::Token.encode_credentials(server_auth["token"]) - end - - def logout(params=nil, headers=nil) - delete "http://api.lvh.me:3000/1/logout.json", - params || {format: :json}, - headers || auth_headers - end - - def cleanup_user(login = nil) - login ||= @user.login - Identity.by_address.key(login + '@' + APP_CONFIG[:domain]).each do |identity| - identity.destroy - end - if user = User.find_by_login(login) - user.destroy - end - end - - def user_params(params) - if params.keys.include?(:password) - srp_process_password(params) - end - return { user: params, format: :json } - end - - def srp_process_password(params) - params.reverse_merge! login: @login, salt: @salt - @srp = SRP::Client.new params[:login], password: params.delete(:password) - @salt = srp.salt.to_s(16) - params.merge! :password_verifier => srp.verifier.to_s(16), - :password_salt => @salt - end - - def srp(params = nil) - if params.nil? - @srp - else - params.reverse_merge! password: @password - SRP::Client.new(params.delete(:login) || @login, params) - end - end -end diff --git a/users/test/integration/api/update_account_test.rb b/users/test/integration/api/update_account_test.rb deleted file mode 100644 index 63429e7..0000000 --- a/users/test/integration/api/update_account_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'test_helper' -require_relative 'srp_test' - -class UpdateAccountTest < SrpTest - - setup do - register_user - end - - test "require authentication" do - update_user password: "No! Verify me instead." - assert_access_denied - end - - test "require token" do - authenticate - put "http://api.lvh.me:3000/1/users/" + @user.id + '.json', - user_params(password: "No! Verify me instead.") - assert_access_denied - end - - test "update password via api" do - authenticate - update_user password: "No! Verify me instead." - authenticate - assert last_response.successful? - assert_nil server_auth["errors"] - assert server_auth["M2"] - end - - test "change login with password_verifier" do - authenticate - new_login = 'zaph' - cleanup_user new_login - update_user login: new_login, password: @password - authenticate - assert last_response.successful? - assert_equal new_login, @user.reload.login - end - - test "prevent changing login without changing password_verifier" do - authenticate - original_login = @user.login - new_login = 'zaph' - cleanup_user new_login - update_user login: new_login - assert last_response.successful? - # does not change login if no password_verifier is present - assert_equal original_login, @user.reload.login - end -end diff --git a/users/test/integration/browser/account_test.rb b/users/test/integration/browser/account_test.rb deleted file mode 100644 index a5677ad..0000000 --- a/users/test/integration/browser/account_test.rb +++ /dev/null @@ -1,147 +0,0 @@ -require 'test_helper' - -class AccountTest < BrowserIntegrationTest - - teardown do - Identity.destroy_all_disabled - end - - test "normal account workflow" do - username, password = submit_signup - assert page.has_content?("Welcome #{username}") - click_on 'Logout' - assert page.has_content?("Log In") - assert_equal '/', current_path - assert user = User.find_by_login(username) - user.account.destroy - end - - test "successful login" do - username, password = submit_signup - click_on 'Logout' - attempt_login(username, password) - assert page.has_content?("Welcome #{username}") - within('.sidenav li.active') do - assert page.has_content?("Overview") - end - User.find_by_login(username).account.destroy - end - - test "failed login" do - visit '/' - attempt_login("username", "wrong password") - assert_invalid_login(page) - end - - test "account destruction" do - username, password = submit_signup - click_on I18n.t('account_settings') - click_on I18n.t('destroy_my_account') - assert page.has_content?(I18n.t('account_destroyed')) - attempt_login(username, password) - assert_invalid_login(page) - end - - test "handle blocked after account destruction" do - username, password = submit_signup - click_on I18n.t('account_settings') - click_on I18n.t('destroy_my_account') - submit_signup(username) - assert page.has_content?('has already been taken') - end - - test "default user actions" do - username, password = submit_signup - click_on "Account Settings" - assert page.has_content? I18n.t('destroy_my_account') - assert page.has_no_css? '#update_login_and_password' - assert page.has_no_css? '#update_pgp_key' - end - - test "default admin actions" do - username, password = submit_signup - with_config admins: [username] do - click_on "Account Settings" - assert page.has_content? I18n.t('destroy_my_account') - assert page.has_no_css? '#update_login_and_password' - assert page.has_css? '#update_pgp_key' - end - end - - test "change password" do - with_config user_actions: ['change_password'] do - username, password = submit_signup - click_on "Account Settings" - within('#update_login_and_password') do - fill_in 'Password', with: "other password" - fill_in 'Password confirmation', with: "other password" - click_on 'Save' - end - click_on 'Logout' - attempt_login(username, "other password") - assert page.has_content?("Welcome #{username}") - User.find_by_login(username).account.destroy - end - end - - test "change pgp key" do - with_config user_actions: ['change_pgp_key'] do - pgp_key = FactoryGirl.build :pgp_key - username, password = submit_signup - click_on "Account Settings" - within('#update_pgp_key') do - fill_in 'Public key', with: pgp_key - click_on 'Save' - end - page.assert_selector 'input[value="Saving..."]' - # at some point we're done: - page.assert_no_selector 'input[value="Saving..."]' - assert page.has_field? 'Public key', with: pgp_key.to_s - user = User.find_by_login(username) - assert_equal pgp_key, user.public_key - user.account.destroy - end - end - - - # trying to seed an invalid A for srp login - test "detects attempt to circumvent SRP" do - user = FactoryGirl.create :user - visit '/login' - fill_in 'Username', with: user.login - fill_in 'Password', with: "password" - inject_malicious_js - click_on 'Log In' - assert page.has_content?("Invalid random key") - assert page.has_no_content?("Welcome") - user.destroy - end - - test "reports internal server errors" do - V1::UsersController.any_instance.stubs(:create).raises - submit_signup - assert page.has_content?("server failed") - end - - def attempt_login(username, password) - click_on 'Log In' - fill_in 'Username', with: username - fill_in 'Password', with: password - click_on 'Log In' - end - - def assert_invalid_login(page) - assert page.has_selector? 'input.btn-primary.disabled' - assert page.has_content? I18n.t(:invalid_user_pass) - assert page.has_no_selector? 'input.btn-primary.disabled' - end - - def inject_malicious_js - page.execute_script <<-EOJS - var calc = new srp.Calculate(); - calc.A = function(_a) {return "00";}; - calc.S = calc.A; - srp.session = new srp.Session(null, calc); - EOJS - end -end diff --git a/users/test/integration/browser/session_test.rb b/users/test/integration/browser/session_test.rb deleted file mode 100644 index 3a41b3a..0000000 --- a/users/test/integration/browser/session_test.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'test_helper' - -class SessionTest < BrowserIntegrationTest - - setup do - @username, password = submit_signup - end - - teardown do - user = User.find_by_login(@username) - id = user.identity - id.destroy - user.destroy - end - - test "valid session" do - assert page.has_content?("Welcome #{@username}") - end - - test "expired session" do - assert page.has_content?("Welcome #{@username}") - pretend_now_is(Time.now + 40.minutes) do - visit '/' - assert page.has_no_content?("Welcome #{@username}") - end - end -end diff --git a/users/test/integration/navigation_test.rb b/users/test/integration/navigation_test.rb deleted file mode 100644 index eec8c0e..0000000 --- a/users/test/integration/navigation_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -class NavigationTest < ActionDispatch::IntegrationTest - - # test "the truth" do - # assert true - # end -end - diff --git a/users/test/leap_web_users_test.rb b/users/test/leap_web_users_test.rb deleted file mode 100644 index f142e54..0000000 --- a/users/test/leap_web_users_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class LeapWebUsersTest < ActiveSupport::TestCase - test "module exists" do - assert_kind_of Module, LeapWebUsers - end -end diff --git a/users/test/support/auth_test_helper.rb b/users/test/support/auth_test_helper.rb deleted file mode 100644 index 57f9f9b..0000000 --- a/users/test/support/auth_test_helper.rb +++ /dev/null @@ -1,65 +0,0 @@ -module AuthTestHelper - extend ActiveSupport::Concern - - # Controller will fetch current user from warden. - # Make it pick up our current_user - included do - setup do - request.env['warden'] ||= stub :user => nil - end - end - - def login(user_or_method_hash = {}) - if user_or_method_hash.respond_to?(:reverse_merge) - user_or_method_hash.reverse_merge! :is_admin? => false - end - @current_user = stub_record(:user, user_or_method_hash) - request.env['warden'] = stub :user => @current_user - request.env['HTTP_AUTHORIZATION'] = header_for_token_auth - return @current_user - end - - def assert_access_denied(denied = true, logged_in = true) - if denied - if @response.content_type == 'application/json' - assert_json_response('error' => I18n.t(:not_authorized)) - assert_response :unprocessable_entity - else - if logged_in - assert_equal({:alert => I18n.t(:not_authorized)}, flash.to_hash) - assert_redirected_to home_url - else - assert_equal({:alert => I18n.t(:not_authorized_login)}, flash.to_hash) - assert_redirected_to login_url - end - end - else - assert flash[:alert].blank? - end - end - - def expect_logout - expect_warden_logout - @token.expects(:destroy) if @token - end - - protected - - def header_for_token_auth - @token = find_record(:token, :authenticate => @current_user) - ActionController::HttpAuthentication::Token.encode_credentials @token.id - end - - def expect_warden_logout - raw = mock('raw session') do - expects(:inspect) - end - request.env['warden'].expects(:raw_session).returns(raw) - request.env['warden'].expects(:logout) - end - -end - -class ActionController::TestCase - include AuthTestHelper -end diff --git a/users/test/support/stub_record_helper.rb b/users/test/support/stub_record_helper.rb deleted file mode 100644 index 25138a0..0000000 --- a/users/test/support/stub_record_helper.rb +++ /dev/null @@ -1,53 +0,0 @@ -module StubRecordHelper - - # - # We will stub find when called on the records class and - # return the record given. - # - # If no record is given but a hash or nil will create a stub based on - # that instead and returns the stub. - # - def find_record(factory, record_or_attribs_hash = {}) - record = stub_record factory, record_or_attribs_hash, true - klass = record.class - # find is just an alias for get with CouchRest Model - klass.stubs(:get).with(record.to_param.to_s).returns(record) - klass.stubs(:find).with(record.to_param.to_s).returns(record) - return record - end - - # Create a stub that has the usual functions of a database record. - # It won't fail on rendering a form for example. - # - # If the second parameter is a record we return the record itself. - # This way you can build functions that either take a record or a - # method hash to stub from. See find_record for an example. - def stub_record(factory, record_or_method_hash = {}, persisted=false) - if record_or_method_hash && !record_or_method_hash.is_a?(Hash) - return record_or_method_hash - end - FactoryGirl.build_stubbed(factory).tap do |record| - if persisted or record.persisted? - record_or_method_hash.reverse_merge! :created_at => Time.now, - :updated_at => Time.now, :id => Random.rand(100000).to_s - end - record.stubs(record_or_method_hash) if record_or_method_hash.present? - end - end - - # returns deep stringified attributes so they can be compared to - # what the controller receives as params - def record_attributes_for(factory, attribs_hash = nil) - FactoryGirl.attributes_for(factory, attribs_hash).tap do |attribs| - attribs.keys.each do |key| - val = attribs.delete(key) - attribs[key.to_s] = val.is_a?(Hash) ? val.stringify_keys! : val - end - end - end - -end - -class ActionController::TestCase - include StubRecordHelper -end diff --git a/users/test/support/time_test_helper.rb b/users/test/support/time_test_helper.rb deleted file mode 100644 index f673f12..0000000 --- a/users/test/support/time_test_helper.rb +++ /dev/null @@ -1,30 +0,0 @@ -# Extend the Time class so that we can offset the time that 'now' -# returns. This should allow us to effectively time warp for functional -# tests that require limits per hour, what not. -class Time #:nodoc: - class < 0 - Identity.destroy_all_disabled - assert_equal 0, Identity.disabled.count - end - - def alias_name - @alias_name ||= Faker::Internet.user_name - end - - def forward_address - @forward_address ||= Faker::Internet.email - end - - def pgp_key_string - @pgp_key ||= "DUMMY PGP KEY ... "+SecureRandom.base64(4096) - end -end diff --git a/users/test/unit/local_email_test.rb b/users/test/unit/local_email_test.rb deleted file mode 100644 index 20ee7f1..0000000 --- a/users/test/unit/local_email_test.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'test_helper' - -class LocalEmailTest < ActiveSupport::TestCase - - test "appends domain" do - local = LocalEmail.new(handle) - assert_equal LocalEmail.new(email), local - assert local.valid? - end - - test "returns handle" do - local = LocalEmail.new(email) - assert_equal handle, local.handle - end - - test "prints full email" do - local = LocalEmail.new(handle) - assert_equal email, "#{local}" - end - - test "validates domain" do - local = LocalEmail.new(Faker::Internet.email) - assert !local.valid? - assert_equal ["needs to end in @#{LocalEmail.domain}"], local.errors[:email] - end - - test "blacklists rfc2142" do - black_listed = LocalEmail.new('hostmaster') - assert !black_listed.valid? - end - - test "blacklists etc passwd" do - black_listed = LocalEmail.new('nobody') - assert !black_listed.valid? - end - - test "whitelist overwrites automatic blacklists" do - with_config handle_whitelist: ['nobody', 'hostmaster'] do - white_listed = LocalEmail.new('nobody') - assert white_listed.valid? - white_listed = LocalEmail.new('hostmaster') - assert white_listed.valid? - end - end - - test "blacklists from config" do - black_listed = LocalEmail.new('www-data') - assert !black_listed.valid? - end - - test "blacklist from config overwrites whitelist" do - with_config handle_whitelist: ['www-data'] do - black_listed = LocalEmail.new('www-data') - assert !black_listed.valid? - end - end - - def handle - @handle ||= Faker::Internet.user_name - end - - def email - handle + "@" + APP_CONFIG[:domain] - end -end diff --git a/users/test/unit/token_test.rb b/users/test/unit/token_test.rb deleted file mode 100644 index a3c6cf6..0000000 --- a/users/test/unit/token_test.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'test_helper' - -class ClientCertificateTest < ActiveSupport::TestCase - include StubRecordHelper - - setup do - @user = find_record :user - end - - test "new token for user" do - sample = Token.new(:user_id => @user.id) - assert sample.valid? - assert_equal @user.id, sample.user_id - assert_equal @user, sample.authenticate - end - - test "token id is secure" do - sample = Token.new(:user_id => @user.id) - other = Token.new(:user_id => @user.id) - assert sample.id, - "id is set on initialization" - assert sample.id[0..10] != other.id[0..10], - "token id prefixes should not repeat" - assert /[g-zG-Z]/.match(sample.id), - "should use non hex chars in the token id" - assert sample.id.size > 16, - "token id should be more than 16 chars long" - end - - test "token checks for user" do - sample = Token.new - assert !sample.valid?, "Token should require a user record" - end - - test "token updates timestamps" do - sample = Token.new(user_id: @user.id) - sample.last_seen_at = 1.minute.ago - sample.expects(:save) - assert_equal @user, sample.authenticate - assert Time.now - sample.last_seen_at < 1.minute, "last_seen_at has not been updated" - end - - test "token will not expire if token_expires_after is not set" do - sample = Token.new(user_id: @user.id) - sample.last_seen_at = 2.years.ago - with_config auth: {} do - sample.expects(:save) - assert_equal @user, sample.authenticate - end - end - - test "expired token returns nil on authenticate" do - sample = Token.new(user_id: @user.id) - sample.last_seen_at = 2.hours.ago - with_config auth: {token_expires_after: 60} do - sample.expects(:destroy) - assert_nil sample.authenticate - end - end - - test "Token.destroy_all_expired is noop if no expiry is set" do - expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago - with_config auth: {} do - Token.destroy_all_expired - end - assert_equal expired, Token.find(expired.id) - end - - test "Token.destroy_all_expired cleans up expired tokens only" do - expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago - fresh = FactoryGirl.create :token - with_config auth: {token_expires_after: 60} do - Token.destroy_all_expired - end - assert_nil Token.find(expired.id) - assert_equal fresh, Token.find(fresh.id) - fresh.destroy - end - - - test "Token.destroy_all_expired does not interfere with expired.authenticate" do - expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago - with_config auth: {token_expires_after: 60} do - Token.destroy_all_expired - end - assert_nil expired.authenticate - end - -end diff --git a/users/test/unit/unauthenticated_user_test.rb b/users/test/unit/unauthenticated_user_test.rb deleted file mode 100644 index e5fafb8..0000000 --- a/users/test/unit/unauthenticated_user_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class UnauthenticatedUserTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end diff --git a/users/test/unit/user_test.rb b/users/test/unit/user_test.rb deleted file mode 100644 index ffbb7d8..0000000 --- a/users/test/unit/user_test.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'test_helper' - -class UserTest < ActiveSupport::TestCase - - include SRP::Util - setup do - @user = FactoryGirl.build(:user) - end - - test "design docs in database are authorative" do - assert !User.design_doc.auto_update, - "Automatic update of design docs should be disabled" - end - - test "test set of attributes should be valid" do - @user.valid? - assert_equal Hash.new, @user.errors.messages - end - - test "test require hex for password_verifier" do - @user.password_verifier = "QWER" - assert !@user.valid? - end - - test "test require alphanumerical for login" do - @user.login = "qw#r" - assert !@user.valid? - end - - test "verifier returns number for the hex in password_verifier" do - assert_equal @user.password_verifier.hex, @user.verifier - end - - test "salt returns number for the hex in password_salt" do - assert_equal @user.password_salt.hex, @user.salt - end - - test 'normal user is no admin' do - assert !@user.is_admin? - end - - test 'user with login in APP_CONFIG is an admin' do - admin_login = APP_CONFIG['admins'].first - @user.login = admin_login - assert @user.is_admin? - end - - test "login needs to be unique" do - other_user = FactoryGirl.create :user, login: @user.login - assert !@user.valid? - other_user.destroy - end - - test "login needs to be unique amongst aliases" do - other_user = FactoryGirl.create :user - id = Identity.create_for other_user, address: @user.login - assert !@user.valid? - id.destroy - other_user.destroy - end - - test "deprecated public key api still works" do - key = SecureRandom.base64(4096) - @user.public_key = key - assert_equal key, @user.public_key - end - -end diff --git a/users/test/unit/warden_strategy_secure_remote_password_test.rb b/users/test/unit/warden_strategy_secure_remote_password_test.rb deleted file mode 100644 index e6fcfbe..0000000 --- a/users/test/unit/warden_strategy_secure_remote_password_test.rb +++ /dev/null @@ -1,63 +0,0 @@ -class WardenStrategySecureRemotePasswordTest < ActiveSupport::TestCase - -# TODO : turn this into sth. real -=begin - setup do - @user = stub :login => "me", :id => 123 - @client_hex = 'a123' - @client_rnd = @client_hex.hex - @server_hex = 'b123' - @server_rnd = @server_hex.hex - @server_rnd_exp = 'e123'.hex - @salt = 'stub user salt' - @server_handshake = stub :aa => @client_rnd, :bb => @server_rnd, :b => @server_rnd_exp - @server_auth = 'adfe' - end - - - test "should perform handshake" do - @user.expects(:initialize_auth). - with(@client_rnd). - returns(@server_handshake) - @server_handshake.expects(:to_json). - returns({'B' => @server_hex, 'salt' => @salt}.to_json) - User.expects(:find).with(@user.login).returns(@user) - assert_equal @server_handshake, session[:handshake] - assert_response :success - assert_json_response :B => @server_hex, :salt => @salt - end - - test "should report user not found" do - unknown = "login_that_does_not_exist" - User.expects(:find).with(unknown).raises(RECORD_NOT_FOUND) - post :create, :login => unknown - assert_response :success - assert_json_error "login" => ["unknown user"] - end - - test "should authorize" do - session[:handshake] = @server_handshake - @server_handshake.expects(:authenticate!). - with(@client_rnd). - returns(@user) - @server_handshake.expects(:to_json). - returns({:M2 => @server_auth}.to_json) - post :update, :id => @user.login, :client_auth => @client_hex - assert_nil session[:handshake] - assert_json_response :M2 => @server_auth - assert_equal @user.id, session[:user_id] - end - - test "should report wrong password" do - session[:handshake] = @server_handshake - @server_handshake.expects(:authenticate!). - with(@client_rnd). - raises(WRONG_PASSWORD) - post :update, :id => @user.login, :client_auth => @client_hex - assert_nil session[:handshake] - assert_nil session[:user_id] - assert_json_error "password" => ["wrong password"] - end - -=end -end diff --git a/users/test/unit/webfinger/host_meta_presenter_test.rb b/users/test/unit/webfinger/host_meta_presenter_test.rb deleted file mode 100644 index af86404..0000000 --- a/users/test/unit/webfinger/host_meta_presenter_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'test_helper' -require 'webfinger' -require 'json' - -class Webfinger::HostMetaPresenterTest < ActiveSupport::TestCase - - setup do - @request = stub( - url: "https://#{APP_CONFIG[:domain]}/.well-known/host-meta" - ) - @meta = Webfinger::HostMetaPresenter.new(@request) - end - - test "creates proper json" do - hash = JSON.parse @meta.to_json - assert_equal ["subject", "links"].sort, hash.keys.sort - hash.each do |key, value| - assert_equal @meta.send(key.to_sym).to_json, value.to_json - end - end - -end - - diff --git a/users/test/unit/webfinger/user_presenter_test.rb b/users/test/unit/webfinger/user_presenter_test.rb deleted file mode 100644 index 04aeb22..0000000 --- a/users/test/unit/webfinger/user_presenter_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'test_helper' -require 'webfinger' -require 'json' - -class Webfinger::UserPresenterTest < ActiveSupport::TestCase - - - setup do - @user = stub( - username: 'testuser', - email_address: "testuser@#{APP_CONFIG[:domain]}" - ) - @request = stub( - host: APP_CONFIG[:domain] - ) - end - - test "user without key has no links" do - @user.stubs :public_key => nil - presenter = Webfinger::UserPresenter.new(@user, @request) - assert_equal Hash.new, presenter.links - end - - test "user with key has corresponding link" do - @user.stubs :public_key => "here's a key" - presenter = Webfinger::UserPresenter.new(@user, @request) - assert_equal [:public_key], presenter.links.keys - assert_equal "PGP", presenter.links[:public_key][:type] - assert_equal presenter.send(:key), presenter.links[:public_key][:href] - end - - test "key is base64 encoded" do - @user.stubs :public_key => "here's a key" - presenter = Webfinger::UserPresenter.new(@user, @request) - assert_equal Base64.encode64(@user.public_key), presenter.send(:key) - end - - test "creates proper json representation" do - @user.stubs :public_key => "here's a key" - presenter = Webfinger::UserPresenter.new(@user, @request) - hash = JSON.parse presenter.to_json - assert_equal ["subject", "links"].sort, hash.keys.sort - hash.each do |key, value| - assert_equal presenter.send(key.to_sym).to_json, value.to_json - end - end - - -end -- cgit v1.2.3