From d11c63fc2a98036fb64b3fc13b8a2528afb13c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Fri, 23 Sep 2016 15:42:11 +0200 Subject: [PATCH 01/14] Create basic workflow for id site authentication --- .gitignore | 1 + .../rails/id_site/login_controller.rb | 30 +++++++++++++ .../rails/id_site/logout_controller.rb | 44 +++++++++++++++++++ .../stormpath/rails/login/new_controller.rb | 11 ++++- lib/stormpath/rails/router.rb | 10 ++++- .../app/views/layouts/application.html.erb | 5 +++ spec/dummy/config/stormpath.yml | 2 +- 7 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 app/controllers/stormpath/rails/id_site/login_controller.rb create mode 100644 app/controllers/stormpath/rails/id_site/logout_controller.rb diff --git a/.gitignore b/.gitignore index 12dea17..a6f0c0b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ spec/dummy/db/development.sqlite3 spec/dummy/log/development.log spec/dummy/log/test.log spec/dummy/tmp +docs diff --git a/app/controllers/stormpath/rails/id_site/login_controller.rb b/app/controllers/stormpath/rails/id_site/login_controller.rb new file mode 100644 index 0000000..f37670c --- /dev/null +++ b/app/controllers/stormpath/rails/id_site/login_controller.rb @@ -0,0 +1,30 @@ +module Stormpath + module Rails + module IdSite + class LoginController < BaseController + before_action :require_no_authentication! + + def call + begin + jwt = JWT.decode(params[:jwtResponse], ENV['STORMPATH_API_KEY_SECRET'], 'HS256') + account = Stormpath::Rails::Client.client.accounts.get(jwt.first['sub']) + login_the_account(account) + redirect_to root_path + rescue Stormpath::Error, LoginForm::FormError => error + binding.pry + end + end + + private + + def login_the_account(account) + AccountLoginWithStormpathToken.new( + cookies, account, + Stormpath::Rails::Client.application, + Stormpath::Rails::Client.client.data_store.api_key + ).call + end + end + end + end +end diff --git a/app/controllers/stormpath/rails/id_site/logout_controller.rb b/app/controllers/stormpath/rails/id_site/logout_controller.rb new file mode 100644 index 0000000..661e8ba --- /dev/null +++ b/app/controllers/stormpath/rails/id_site/logout_controller.rb @@ -0,0 +1,44 @@ +module Stormpath + module Rails + module IdSite + class LogoutController < BaseController + def call + begin + payload = { 'iat' => Time.now.to_i, + 'iss' => ENV['STORMPATH_API_KEY_ID'], + 'sub' => ENV['STORMPATH_APPLICATION_URL'], + 'cb_uri' => 'http://localhost:3000/', + 'jti' => SecureRandom.uuid, + 'path' => '/', + 'state' => '' } + secret = ENV['STORMPATH_API_KEY_SECRET'] + jwt = JWT.encode(payload, secret, 'HS256') + delete_tokens + delete_cookies + redirect_to "https://api.stormpath.com/sso/logout?jwtRequest=#{jwt}" + rescue Stormpath::Error, LoginForm::FormError => error + end + end + + private + def delete_tokens + DeleteAccessToken.call(cookies[access_token_cookie_name]) + DeleteRefreshToken.call(cookies[refresh_token_cookie_name]) + end + + def delete_cookies + cookies.delete(access_token_cookie_name) + cookies.delete(refresh_token_cookie_name) + end + + def access_token_cookie_name + stormpath_config.web.access_token_cookie.name + end + + def refresh_token_cookie_name + stormpath_config.web.refresh_token_cookie.name + end + end + end + end +end diff --git a/app/controllers/stormpath/rails/login/new_controller.rb b/app/controllers/stormpath/rails/login/new_controller.rb index 30169b4..4860d91 100644 --- a/app/controllers/stormpath/rails/login/new_controller.rb +++ b/app/controllers/stormpath/rails/login/new_controller.rb @@ -6,7 +6,16 @@ class NewController < BaseController def call if stormpath_config.web.id_site.enabled - redirect_to id_site_login_url + payload = { 'iat' => Time.now.to_i, + 'iss' => ENV['STORMPATH_API_KEY_ID'], + 'sub' => ENV['STORMPATH_APPLICATION_URL'], + 'cb_uri' => 'http://localhost:3000/id_site_result', + 'jti' => SecureRandom.uuid, + 'path' => '/', + 'state' => '' } + secret = ENV['STORMPATH_API_KEY_SECRET'] + jwt = JWT.encode(payload, secret, 'HS256') + redirect_to "https://api.stormpath.com/sso?jwtRequest=#{jwt}" else respond_to do |format| format.json { render json: LoginNewSerializer.to_h } diff --git a/lib/stormpath/rails/router.rb b/lib/stormpath/rails/router.rb index 9513c43..bf162cd 100644 --- a/lib/stormpath/rails/router.rb +++ b/lib/stormpath/rails/router.rb @@ -15,7 +15,9 @@ module Router 'oauth2#new' => 'stormpath/rails/oauth2/new#call', 'oauth2#create' => 'stormpath/rails/oauth2/create#call', 'verify_email#show' => 'stormpath/rails/verify_email/show#call', - 'verify_email#create' => 'stormpath/rails/verify_email/create#call' + 'verify_email#create' => 'stormpath/rails/verify_email/create#call', + 'id_site#login' => 'stormpath/rails/id_site/login#call', + 'id_site#logout' => 'stormpath/rails/id_site/logout#call' }.freeze def stormpath_rails_routes(actions: {}) @@ -66,6 +68,12 @@ def stormpath_rails_routes(actions: {}) get Stormpath::Rails.config.web.verify_email.uri => actions['verify_email#show'], as: :new_verify_email post Stormpath::Rails.config.web.verify_email.uri => actions['verify_email#create'], as: :verify_email end + + # ID SITE LOGIN + if Stormpath::Rails.config.web.id_site.enabled + get '/id_site_result' => actions['id_site#login'], as: :id_site_result + get '/logout_id_site' => actions['id_site#logout'], as: :logout_id_site + end end end end diff --git a/spec/dummy/app/views/layouts/application.html.erb b/spec/dummy/app/views/layouts/application.html.erb index 593a778..9c45a2f 100644 --- a/spec/dummy/app/views/layouts/application.html.erb +++ b/spec/dummy/app/views/layouts/application.html.erb @@ -8,6 +8,11 @@ + <% if signed_in? %> +

Logged in as: <%= current_account.given_name %>

+ <%= link_to "Log out", logout_id_site_path %> + <% end %> + <%= yield %> diff --git a/spec/dummy/config/stormpath.yml b/spec/dummy/config/stormpath.yml index ae865da..1339129 100644 --- a/spec/dummy/config/stormpath.yml +++ b/spec/dummy/config/stormpath.yml @@ -179,7 +179,7 @@ stormpath: # login, registration, and password reset. They should also be redirected # through ID Site on logout. idSite: - enabled: false + enabled: true loginUri: "" forgotUri: "/#/forgot" registerUri: "/#/register" From 6ac5748ea86ad980b29bd26111caed85c10b63a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Mon, 26 Sep 2016 12:07:43 +0200 Subject: [PATCH 02/14] Validate jwt response from stormpath after login --- .../rails/id_site/login_controller.rb | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/app/controllers/stormpath/rails/id_site/login_controller.rb b/app/controllers/stormpath/rails/id_site/login_controller.rb index f37670c..22ae459 100644 --- a/app/controllers/stormpath/rails/id_site/login_controller.rb +++ b/app/controllers/stormpath/rails/id_site/login_controller.rb @@ -9,9 +9,9 @@ def call jwt = JWT.decode(params[:jwtResponse], ENV['STORMPATH_API_KEY_SECRET'], 'HS256') account = Stormpath::Rails::Client.client.accounts.get(jwt.first['sub']) login_the_account(account) - redirect_to root_path - rescue Stormpath::Error, LoginForm::FormError => error - binding.pry + respond_with_success + rescue Stormpath::Error, JWT::VerificationError, JWT::ExpiredSignature => error + respond_with_error(error) end end @@ -24,6 +24,33 @@ def login_the_account(account) Stormpath::Rails::Client.client.data_store.api_key ).call end + + def respond_with_success + respond_to do |format| + format.html { redirect_to login_redirect_route, notice: 'Successfully signed in' } + format.json { render json: serialized_account } + end + end + + def respond_with_error(error) + respond_to do |format| + format.html do + flash.now[:error] = error.message + render stormpath_config.web.login.view + end + format.json do + render json: { status: error.status, message: error.message }, status: error.status + end + end + end + + def login_redirect_route + if params[:next] + URI(params[:next]).path + else + stormpath_config.web.login.next_uri + end + end end end end From 3efa73e77379bc43e6b6a47f9ade30c30d7d70ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Mon, 26 Sep 2016 12:48:20 +0200 Subject: [PATCH 03/14] Adjust register controller for ID site workflow --- .../stormpath/rails/register/new_controller.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/controllers/stormpath/rails/register/new_controller.rb b/app/controllers/stormpath/rails/register/new_controller.rb index 5a0ff63..59ad291 100644 --- a/app/controllers/stormpath/rails/register/new_controller.rb +++ b/app/controllers/stormpath/rails/register/new_controller.rb @@ -4,7 +4,16 @@ module Register class NewController < BaseController def call if stormpath_config.web.id_site.enabled - redirect_to id_site_register_url + payload = { 'iat' => Time.now.to_i, + 'iss' => ENV['STORMPATH_API_KEY_ID'], + 'sub' => ENV['STORMPATH_APPLICATION_URL'], + 'cb_uri' => 'http://localhost:3000/id_site_result', + 'jti' => SecureRandom.uuid, + 'path' => '/#/register', + 'state' => '' } + secret = ENV['STORMPATH_API_KEY_SECRET'] + jwt = JWT.encode(payload, secret, 'HS256') + redirect_to "https://api.stormpath.com/sso?jwtRequest=#{jwt}" elsif signed_in? redirect_to root_path else From 6bbc29035479b91bd664631551c6efa7013cd313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Mon, 26 Sep 2016 13:11:33 +0200 Subject: [PATCH 04/14] Use variables instead of hardcoded strings in id site jwt payload --- app/controllers/stormpath/rails/id_site/logout_controller.rb | 4 ++-- app/controllers/stormpath/rails/login/new_controller.rb | 4 ++-- app/controllers/stormpath/rails/register/new_controller.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/stormpath/rails/id_site/logout_controller.rb b/app/controllers/stormpath/rails/id_site/logout_controller.rb index 661e8ba..503863b 100644 --- a/app/controllers/stormpath/rails/id_site/logout_controller.rb +++ b/app/controllers/stormpath/rails/id_site/logout_controller.rb @@ -7,9 +7,9 @@ def call payload = { 'iat' => Time.now.to_i, 'iss' => ENV['STORMPATH_API_KEY_ID'], 'sub' => ENV['STORMPATH_APPLICATION_URL'], - 'cb_uri' => 'http://localhost:3000/', + 'cb_uri' => root_path, 'jti' => SecureRandom.uuid, - 'path' => '/', + 'path' => stormpath_config.web.id_site.loginUri, 'state' => '' } secret = ENV['STORMPATH_API_KEY_SECRET'] jwt = JWT.encode(payload, secret, 'HS256') diff --git a/app/controllers/stormpath/rails/login/new_controller.rb b/app/controllers/stormpath/rails/login/new_controller.rb index 4860d91..c889453 100644 --- a/app/controllers/stormpath/rails/login/new_controller.rb +++ b/app/controllers/stormpath/rails/login/new_controller.rb @@ -9,9 +9,9 @@ def call payload = { 'iat' => Time.now.to_i, 'iss' => ENV['STORMPATH_API_KEY_ID'], 'sub' => ENV['STORMPATH_APPLICATION_URL'], - 'cb_uri' => 'http://localhost:3000/id_site_result', + 'cb_uri' => id_site_result_path, 'jti' => SecureRandom.uuid, - 'path' => '/', + 'path' => stormpath_config.web.id_site.loginUri, 'state' => '' } secret = ENV['STORMPATH_API_KEY_SECRET'] jwt = JWT.encode(payload, secret, 'HS256') diff --git a/app/controllers/stormpath/rails/register/new_controller.rb b/app/controllers/stormpath/rails/register/new_controller.rb index 59ad291..85a8304 100644 --- a/app/controllers/stormpath/rails/register/new_controller.rb +++ b/app/controllers/stormpath/rails/register/new_controller.rb @@ -7,9 +7,9 @@ def call payload = { 'iat' => Time.now.to_i, 'iss' => ENV['STORMPATH_API_KEY_ID'], 'sub' => ENV['STORMPATH_APPLICATION_URL'], - 'cb_uri' => 'http://localhost:3000/id_site_result', + 'cb_uri' => id_site_result_path, 'jti' => SecureRandom.uuid, - 'path' => '/#/register', + 'path' => stormpath_config.web.id_site.registerUri, 'state' => '' } secret = ENV['STORMPATH_API_KEY_SECRET'] jwt = JWT.encode(payload, secret, 'HS256') From 0ab09ccc0b7b14a925df1f92d2c823384ed08632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Mon, 26 Sep 2016 15:57:29 +0200 Subject: [PATCH 05/14] Use url for cb_uri instead of path --- app/controllers/stormpath/rails/login/new_controller.rb | 2 +- app/controllers/stormpath/rails/register/new_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/stormpath/rails/login/new_controller.rb b/app/controllers/stormpath/rails/login/new_controller.rb index c889453..f65b2ae 100644 --- a/app/controllers/stormpath/rails/login/new_controller.rb +++ b/app/controllers/stormpath/rails/login/new_controller.rb @@ -9,7 +9,7 @@ def call payload = { 'iat' => Time.now.to_i, 'iss' => ENV['STORMPATH_API_KEY_ID'], 'sub' => ENV['STORMPATH_APPLICATION_URL'], - 'cb_uri' => id_site_result_path, + 'cb_uri' => id_site_result_url, 'jti' => SecureRandom.uuid, 'path' => stormpath_config.web.id_site.loginUri, 'state' => '' } diff --git a/app/controllers/stormpath/rails/register/new_controller.rb b/app/controllers/stormpath/rails/register/new_controller.rb index 85a8304..b5bcc2f 100644 --- a/app/controllers/stormpath/rails/register/new_controller.rb +++ b/app/controllers/stormpath/rails/register/new_controller.rb @@ -7,7 +7,7 @@ def call payload = { 'iat' => Time.now.to_i, 'iss' => ENV['STORMPATH_API_KEY_ID'], 'sub' => ENV['STORMPATH_APPLICATION_URL'], - 'cb_uri' => id_site_result_path, + 'cb_uri' => id_site_result_url, 'jti' => SecureRandom.uuid, 'path' => stormpath_config.web.id_site.registerUri, 'state' => '' } From 637f046af07af8d10fb931b250bb8de2eb16ae16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Tue, 27 Sep 2016 13:35:25 +0200 Subject: [PATCH 06/14] DRY up the logout controllers and fix failing specs --- .../create_controller.rb} | 4 +-- .../create_controller.rb} | 26 ++------------ .../rails/logout/create_controller.rb | 21 +---------- .../rails/token_and_cookies_cleaner.rb | 36 +++++++++++++++++++ lib/stormpath/rails/router.rb | 8 ++--- .../app/views/layouts/application.html.erb | 2 +- spec/dummy/config/stormpath.yml | 2 +- 7 files changed, 48 insertions(+), 51 deletions(-) rename app/controllers/stormpath/rails/{id_site/login_controller.rb => id_site_login/create_controller.rb} (95%) rename app/controllers/stormpath/rails/{id_site/logout_controller.rb => id_site_logout/create_controller.rb} (53%) create mode 100644 app/services/stormpath/rails/token_and_cookies_cleaner.rb diff --git a/app/controllers/stormpath/rails/id_site/login_controller.rb b/app/controllers/stormpath/rails/id_site_login/create_controller.rb similarity index 95% rename from app/controllers/stormpath/rails/id_site/login_controller.rb rename to app/controllers/stormpath/rails/id_site_login/create_controller.rb index 22ae459..0f2037d 100644 --- a/app/controllers/stormpath/rails/id_site/login_controller.rb +++ b/app/controllers/stormpath/rails/id_site_login/create_controller.rb @@ -1,7 +1,7 @@ module Stormpath module Rails - module IdSite - class LoginController < BaseController + module IdSiteLogin + class CreateController < BaseController before_action :require_no_authentication! def call diff --git a/app/controllers/stormpath/rails/id_site/logout_controller.rb b/app/controllers/stormpath/rails/id_site_logout/create_controller.rb similarity index 53% rename from app/controllers/stormpath/rails/id_site/logout_controller.rb rename to app/controllers/stormpath/rails/id_site_logout/create_controller.rb index 503863b..c28f35b 100644 --- a/app/controllers/stormpath/rails/id_site/logout_controller.rb +++ b/app/controllers/stormpath/rails/id_site_logout/create_controller.rb @@ -1,7 +1,7 @@ module Stormpath module Rails - module IdSite - class LogoutController < BaseController + module IdSiteLogout + class CreateController < BaseController def call begin payload = { 'iat' => Time.now.to_i, @@ -13,31 +13,11 @@ def call 'state' => '' } secret = ENV['STORMPATH_API_KEY_SECRET'] jwt = JWT.encode(payload, secret, 'HS256') - delete_tokens - delete_cookies + TokenAndCookiesCleaner.new(cookies).remove redirect_to "https://api.stormpath.com/sso/logout?jwtRequest=#{jwt}" rescue Stormpath::Error, LoginForm::FormError => error end end - - private - def delete_tokens - DeleteAccessToken.call(cookies[access_token_cookie_name]) - DeleteRefreshToken.call(cookies[refresh_token_cookie_name]) - end - - def delete_cookies - cookies.delete(access_token_cookie_name) - cookies.delete(refresh_token_cookie_name) - end - - def access_token_cookie_name - stormpath_config.web.access_token_cookie.name - end - - def refresh_token_cookie_name - stormpath_config.web.refresh_token_cookie.name - end end end end diff --git a/app/controllers/stormpath/rails/logout/create_controller.rb b/app/controllers/stormpath/rails/logout/create_controller.rb index e503b8e..0b1dc4c 100644 --- a/app/controllers/stormpath/rails/logout/create_controller.rb +++ b/app/controllers/stormpath/rails/logout/create_controller.rb @@ -8,8 +8,7 @@ def call if bearer_authorization_header? DeleteAccessToken.call(bearer_access_token) else - delete_tokens - delete_cookies + TokenAndCookiesCleaner.new(cookies).remove end respond_with_success end @@ -28,24 +27,6 @@ def authorization_header request.headers['Authorization'] end - def delete_tokens - DeleteAccessToken.call(cookies[access_token_cookie_name]) - DeleteRefreshToken.call(cookies[refresh_token_cookie_name]) - end - - def delete_cookies - cookies.delete(access_token_cookie_name) - cookies.delete(refresh_token_cookie_name) - end - - def access_token_cookie_name - stormpath_config.web.access_token_cookie.name - end - - def refresh_token_cookie_name - stormpath_config.web.refresh_token_cookie.name - end - def respond_with_success respond_to do |format| format.html do diff --git a/app/services/stormpath/rails/token_and_cookies_cleaner.rb b/app/services/stormpath/rails/token_and_cookies_cleaner.rb new file mode 100644 index 0000000..f2b68eb --- /dev/null +++ b/app/services/stormpath/rails/token_and_cookies_cleaner.rb @@ -0,0 +1,36 @@ +module Stormpath + module Rails + class TokenAndCookiesCleaner + attr_reader :cookies + + def initialize(cookies) + @cookies = cookies + end + + def remove + delete_tokens + delete_cookies + end + + private + + def delete_tokens + DeleteAccessToken.call(cookies[access_token_cookie_name]) + DeleteRefreshToken.call(cookies[refresh_token_cookie_name]) + end + + def delete_cookies + cookies.delete(access_token_cookie_name) + cookies.delete(refresh_token_cookie_name) + end + + def access_token_cookie_name + Stormpath::Rails.config.web.access_token_cookie.name + end + + def refresh_token_cookie_name + Stormpath::Rails.config.web.refresh_token_cookie.name + end + end + end +end diff --git a/lib/stormpath/rails/router.rb b/lib/stormpath/rails/router.rb index bf162cd..620ecfa 100644 --- a/lib/stormpath/rails/router.rb +++ b/lib/stormpath/rails/router.rb @@ -16,8 +16,8 @@ module Router 'oauth2#create' => 'stormpath/rails/oauth2/create#call', 'verify_email#show' => 'stormpath/rails/verify_email/show#call', 'verify_email#create' => 'stormpath/rails/verify_email/create#call', - 'id_site#login' => 'stormpath/rails/id_site/login#call', - 'id_site#logout' => 'stormpath/rails/id_site/logout#call' + 'id_site_login#create' => 'stormpath/rails/id_site_login/create#call', + 'id_site_logout#create' => 'stormpath/rails/id_site_logout/create#call' }.freeze def stormpath_rails_routes(actions: {}) @@ -71,8 +71,8 @@ def stormpath_rails_routes(actions: {}) # ID SITE LOGIN if Stormpath::Rails.config.web.id_site.enabled - get '/id_site_result' => actions['id_site#login'], as: :id_site_result - get '/logout_id_site' => actions['id_site#logout'], as: :logout_id_site + get '/id_site_result' => actions['id_site_login#create'], as: :id_site_result + get '/logout_id_site' => actions['id_site_logout#create'], as: :logout_id_site end end end diff --git a/spec/dummy/app/views/layouts/application.html.erb b/spec/dummy/app/views/layouts/application.html.erb index 9c45a2f..781bcfa 100644 --- a/spec/dummy/app/views/layouts/application.html.erb +++ b/spec/dummy/app/views/layouts/application.html.erb @@ -8,7 +8,7 @@ - <% if signed_in? %> + <% if signed_in? and Stormpath::Rails.config.web.id_site.enabled %>

Logged in as: <%= current_account.given_name %>

<%= link_to "Log out", logout_id_site_path %> <% end %> diff --git a/spec/dummy/config/stormpath.yml b/spec/dummy/config/stormpath.yml index 1339129..ae865da 100644 --- a/spec/dummy/config/stormpath.yml +++ b/spec/dummy/config/stormpath.yml @@ -179,7 +179,7 @@ stormpath: # login, registration, and password reset. They should also be redirected # through ID Site on logout. idSite: - enabled: true + enabled: false loginUri: "" forgotUri: "/#/forgot" registerUri: "/#/register" From 8f53a5b41a56d9f841a00e2c2de3dba4df041c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Tue, 27 Sep 2016 14:39:29 +0200 Subject: [PATCH 07/14] Refactor building payload and jwt for id site into service object --- .../rails/id_site_login/create_controller.rb | 6 ++- .../rails/id_site_logout/create_controller.rb | 10 +---- .../stormpath/rails/login/new_controller.rb | 10 +---- .../rails/register/new_controller.rb | 10 +---- .../stormpath/rails/payload_builder.rb | 38 +++++++++++++++++++ 5 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 app/services/stormpath/rails/payload_builder.rb diff --git a/app/controllers/stormpath/rails/id_site_login/create_controller.rb b/app/controllers/stormpath/rails/id_site_login/create_controller.rb index 0f2037d..a6b48ea 100644 --- a/app/controllers/stormpath/rails/id_site_login/create_controller.rb +++ b/app/controllers/stormpath/rails/id_site_login/create_controller.rb @@ -7,7 +7,7 @@ class CreateController < BaseController def call begin jwt = JWT.decode(params[:jwtResponse], ENV['STORMPATH_API_KEY_SECRET'], 'HS256') - account = Stormpath::Rails::Client.client.accounts.get(jwt.first['sub']) + account = Stormpath::Rails::Client.client.accounts.get(account_href(jwt)) login_the_account(account) respond_with_success rescue Stormpath::Error, JWT::VerificationError, JWT::ExpiredSignature => error @@ -51,6 +51,10 @@ def login_redirect_route stormpath_config.web.login.next_uri end end + + def account_href(jwt) + jwt.first['sub'] + end end end end diff --git a/app/controllers/stormpath/rails/id_site_logout/create_controller.rb b/app/controllers/stormpath/rails/id_site_logout/create_controller.rb index c28f35b..d0b82a6 100644 --- a/app/controllers/stormpath/rails/id_site_logout/create_controller.rb +++ b/app/controllers/stormpath/rails/id_site_logout/create_controller.rb @@ -4,15 +4,7 @@ module IdSiteLogout class CreateController < BaseController def call begin - payload = { 'iat' => Time.now.to_i, - 'iss' => ENV['STORMPATH_API_KEY_ID'], - 'sub' => ENV['STORMPATH_APPLICATION_URL'], - 'cb_uri' => root_path, - 'jti' => SecureRandom.uuid, - 'path' => stormpath_config.web.id_site.loginUri, - 'state' => '' } - secret = ENV['STORMPATH_API_KEY_SECRET'] - jwt = JWT.encode(payload, secret, 'HS256') + jwt = PayloadBuilder.new(:logout, cb_uri: root_url).jwt TokenAndCookiesCleaner.new(cookies).remove redirect_to "https://api.stormpath.com/sso/logout?jwtRequest=#{jwt}" rescue Stormpath::Error, LoginForm::FormError => error diff --git a/app/controllers/stormpath/rails/login/new_controller.rb b/app/controllers/stormpath/rails/login/new_controller.rb index f65b2ae..d2ea6a7 100644 --- a/app/controllers/stormpath/rails/login/new_controller.rb +++ b/app/controllers/stormpath/rails/login/new_controller.rb @@ -6,15 +6,7 @@ class NewController < BaseController def call if stormpath_config.web.id_site.enabled - payload = { 'iat' => Time.now.to_i, - 'iss' => ENV['STORMPATH_API_KEY_ID'], - 'sub' => ENV['STORMPATH_APPLICATION_URL'], - 'cb_uri' => id_site_result_url, - 'jti' => SecureRandom.uuid, - 'path' => stormpath_config.web.id_site.loginUri, - 'state' => '' } - secret = ENV['STORMPATH_API_KEY_SECRET'] - jwt = JWT.encode(payload, secret, 'HS256') + jwt = PayloadBuilder.new(:login, cb_uri: id_site_result_url).jwt redirect_to "https://api.stormpath.com/sso?jwtRequest=#{jwt}" else respond_to do |format| diff --git a/app/controllers/stormpath/rails/register/new_controller.rb b/app/controllers/stormpath/rails/register/new_controller.rb index b5bcc2f..a9afdb4 100644 --- a/app/controllers/stormpath/rails/register/new_controller.rb +++ b/app/controllers/stormpath/rails/register/new_controller.rb @@ -4,15 +4,7 @@ module Register class NewController < BaseController def call if stormpath_config.web.id_site.enabled - payload = { 'iat' => Time.now.to_i, - 'iss' => ENV['STORMPATH_API_KEY_ID'], - 'sub' => ENV['STORMPATH_APPLICATION_URL'], - 'cb_uri' => id_site_result_url, - 'jti' => SecureRandom.uuid, - 'path' => stormpath_config.web.id_site.registerUri, - 'state' => '' } - secret = ENV['STORMPATH_API_KEY_SECRET'] - jwt = JWT.encode(payload, secret, 'HS256') + jwt = PayloadBuilder.new(:register, cb_uri: id_site_result_url).jwt redirect_to "https://api.stormpath.com/sso?jwtRequest=#{jwt}" elsif signed_in? redirect_to root_path diff --git a/app/services/stormpath/rails/payload_builder.rb b/app/services/stormpath/rails/payload_builder.rb new file mode 100644 index 0000000..a8b9587 --- /dev/null +++ b/app/services/stormpath/rails/payload_builder.rb @@ -0,0 +1,38 @@ +module Stormpath + module Rails + class PayloadBuilder + attr_reader :resource, :cb_uri + + def initialize(resource, options = {}) + @resource = resource + @cb_uri = options[:cb_uri] + end + + def jwt + JWT.encode(payload, ENV['STORMPATH_API_KEY_SECRET'], 'HS256') + end + + private + + def payload + { + 'iat' => Time.now.to_i, + 'iss' => ENV['STORMPATH_API_KEY_ID'], + 'sub' => ENV['STORMPATH_APPLICATION_URL'], + 'cb_uri' => cb_uri, + 'jti' => SecureRandom.uuid, + 'path' => path, + 'state' => '' + } + end + + def path + if resource == :register + Stormpath::Rails.config.web.id_site.register_uri + else + Stormpath::Rails.config.web.id_site.login_uri + end + end + end + end +end From d1d58a4c1614b14ca20ceaec65f5a9e9b3f336e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Tue, 27 Sep 2016 16:33:05 +0200 Subject: [PATCH 08/14] Add request and payload builder specs --- spec/requests/login/get_spec.rb | 18 +++++++-- spec/requests/registration/get_spec.rb | 18 +++++++-- spec/services/payload_builder_spec.rb | 56 ++++++++++++++++++++++++++ spec/spec_helper.rb | 3 ++ 4 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 spec/services/payload_builder_spec.rb diff --git a/spec/requests/login/get_spec.rb b/spec/requests/login/get_spec.rb index 0ae2877..1b03425 100644 --- a/spec/requests/login/get_spec.rb +++ b/spec/requests/login/get_spec.rb @@ -49,9 +49,21 @@ def json_login_get xit 'login should show account stores' do end - xit 'if id site enabled should redirect' do - json_login_get - expect(response.status).to eq(400) + describe 'if id site is enabled' do + before do + allow(web_config.id_site).to receive(:enabled).and_return(true) + Rails.application.reload_routes! + end + + after do + allow(web_config.id_site).to receive(:enabled).and_return(false) + Rails.application.reload_routes! + end + + it 'should redirect' do + json_login_get + expect(response.status).to eq(302) + end end end diff --git a/spec/requests/registration/get_spec.rb b/spec/requests/registration/get_spec.rb index bbcb43d..642164f 100644 --- a/spec/requests/registration/get_spec.rb +++ b/spec/requests/registration/get_spec.rb @@ -64,9 +64,21 @@ def json_registration_get xit 'register should show account stores' do end - xit 'if id site enabled should redirect' do - json_registration_get - expect(response.status).to eq(400) + describe 'if id site is enabled' do + before do + allow(web_config.id_site).to receive(:enabled).and_return(true) + Rails.application.reload_routes! + end + + after do + allow(web_config.id_site).to receive(:enabled).and_return(false) + Rails.application.reload_routes! + end + + it 'should redirect' do + json_registration_get + expect(response.status).to eq(302) + end end end diff --git a/spec/services/payload_builder_spec.rb b/spec/services/payload_builder_spec.rb new file mode 100644 index 0000000..c9331f5 --- /dev/null +++ b/spec/services/payload_builder_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Stormpath::Rails::PayloadBuilder, type: :service do + Timecop.freeze(Time.zone.now) do + let(:time) { Time.zone.now.to_i } + end + + let(:jwt) do + JWT.encode( + { + 'iat' => time, + 'iss' => ENV['STORMPATH_API_KEY_ID'], + 'sub' => ENV['STORMPATH_APPLICATION_URL'], + 'cb_uri' => cb_uri, + 'jti' => 'secure_random_uuid', + 'path' => path, + 'state' => '' + }, + ENV['STORMPATH_API_KEY_SECRET'], + 'HS256' + ) + end + + before do + allow(SecureRandom).to receive(:uuid).and_return('secure_random_uuid') + allow(web_config.id_site).to receive(:enabled).and_return(true) + Rails.application.reload_routes! + end + + context 'login id site' do + let(:cb_uri) { id_site_result_url } + let(:path) { '' } + + it 'should return correct jwt for login id site' do + expect(Stormpath::Rails::PayloadBuilder.new(:login, cb_uri: id_site_result_url).jwt).to eq jwt + end + end + + context 'when logout id site' do + let(:cb_uri) { root_url } + let(:path) { '' } + + it 'should return correct jwt for logout id site' do + expect(Stormpath::Rails::PayloadBuilder.new(:logout, cb_uri: root_url).jwt).to eq jwt + end + end + + context 'when register id site' do + let(:cb_uri) { id_site_result_url } + let(:path) { '/#/register' } + + it 'should return correct jwt' do + expect(Stormpath::Rails::PayloadBuilder.new(:register, cb_uri: id_site_result_url).jwt).to eq jwt + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0b5d048..01115dd 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -55,6 +55,7 @@ config.include Stormpath::Testing::Helpers, type: :request config.include Stormpath::Testing::Helpers, type: :feature config.include Stormpath::Testing::Helpers, type: :service + config.include Rails.application.routes.url_helpers, type: :service config.include MatchJson::Matchers config.include Capybara::DSL, type: :feature config.include ConfigSpecHelpers @@ -81,3 +82,5 @@ Capybara.register_driver :rack_test do |app| Capybara::RackTest::Driver.new(app, headers: { 'HTTP_ACCEPT' => 'text/html' }) end + +Rails.application.routes.default_url_options[:host]= 'localhost:3000' From fc7b6041183ab3f00ddc528a73517e5d356be6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Thu, 29 Sep 2016 15:46:26 +0200 Subject: [PATCH 09/14] Add login specs after id site authentication --- ...create_controller.rb => new_controller.rb} | 10 +- .../rails/id_site_logout/create_controller.rb | 16 --- .../rails/id_site_logout/new_controller.rb | 13 ++ lib/stormpath/rails/router.rb | 8 +- spec/requests/id_site_login/get_spec.rb | 116 ++++++++++++++++++ 5 files changed, 138 insertions(+), 25 deletions(-) rename app/controllers/stormpath/rails/id_site_login/{create_controller.rb => new_controller.rb} (84%) delete mode 100644 app/controllers/stormpath/rails/id_site_logout/create_controller.rb create mode 100644 app/controllers/stormpath/rails/id_site_logout/new_controller.rb create mode 100644 spec/requests/id_site_login/get_spec.rb diff --git a/app/controllers/stormpath/rails/id_site_login/create_controller.rb b/app/controllers/stormpath/rails/id_site_login/new_controller.rb similarity index 84% rename from app/controllers/stormpath/rails/id_site_login/create_controller.rb rename to app/controllers/stormpath/rails/id_site_login/new_controller.rb index a6b48ea..4b269fa 100644 --- a/app/controllers/stormpath/rails/id_site_login/create_controller.rb +++ b/app/controllers/stormpath/rails/id_site_login/new_controller.rb @@ -1,7 +1,7 @@ module Stormpath module Rails module IdSiteLogin - class CreateController < BaseController + class NewController < BaseController before_action :require_no_authentication! def call @@ -9,7 +9,7 @@ def call jwt = JWT.decode(params[:jwtResponse], ENV['STORMPATH_API_KEY_SECRET'], 'HS256') account = Stormpath::Rails::Client.client.accounts.get(account_href(jwt)) login_the_account(account) - respond_with_success + respond_with_success(account) rescue Stormpath::Error, JWT::VerificationError, JWT::ExpiredSignature => error respond_with_error(error) end @@ -25,10 +25,10 @@ def login_the_account(account) ).call end - def respond_with_success + def respond_with_success(account) respond_to do |format| format.html { redirect_to login_redirect_route, notice: 'Successfully signed in' } - format.json { render json: serialized_account } + format.json { render json: AccountSerializer.to_h(account) } end end @@ -39,7 +39,7 @@ def respond_with_error(error) render stormpath_config.web.login.view end format.json do - render json: { status: error.status, message: error.message }, status: error.status + render json: { message: error.message }, status: error.try(:status) end end end diff --git a/app/controllers/stormpath/rails/id_site_logout/create_controller.rb b/app/controllers/stormpath/rails/id_site_logout/create_controller.rb deleted file mode 100644 index d0b82a6..0000000 --- a/app/controllers/stormpath/rails/id_site_logout/create_controller.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Stormpath - module Rails - module IdSiteLogout - class CreateController < BaseController - def call - begin - jwt = PayloadBuilder.new(:logout, cb_uri: root_url).jwt - TokenAndCookiesCleaner.new(cookies).remove - redirect_to "https://api.stormpath.com/sso/logout?jwtRequest=#{jwt}" - rescue Stormpath::Error, LoginForm::FormError => error - end - end - end - end - end -end diff --git a/app/controllers/stormpath/rails/id_site_logout/new_controller.rb b/app/controllers/stormpath/rails/id_site_logout/new_controller.rb new file mode 100644 index 0000000..a4d745f --- /dev/null +++ b/app/controllers/stormpath/rails/id_site_logout/new_controller.rb @@ -0,0 +1,13 @@ +module Stormpath + module Rails + module IdSiteLogout + class NewController < BaseController + def call + jwt = PayloadBuilder.new(:logout, cb_uri: root_url).jwt + TokenAndCookiesCleaner.new(cookies).remove + redirect_to "https://api.stormpath.com/sso/logout?jwtRequest=#{jwt}" + end + end + end + end +end diff --git a/lib/stormpath/rails/router.rb b/lib/stormpath/rails/router.rb index 620ecfa..4243f95 100644 --- a/lib/stormpath/rails/router.rb +++ b/lib/stormpath/rails/router.rb @@ -16,8 +16,8 @@ module Router 'oauth2#create' => 'stormpath/rails/oauth2/create#call', 'verify_email#show' => 'stormpath/rails/verify_email/show#call', 'verify_email#create' => 'stormpath/rails/verify_email/create#call', - 'id_site_login#create' => 'stormpath/rails/id_site_login/create#call', - 'id_site_logout#create' => 'stormpath/rails/id_site_logout/create#call' + 'id_site_login#new' => 'stormpath/rails/id_site_login/new#call', + 'id_site_logout#new' => 'stormpath/rails/id_site_logout/new#call' }.freeze def stormpath_rails_routes(actions: {}) @@ -71,8 +71,8 @@ def stormpath_rails_routes(actions: {}) # ID SITE LOGIN if Stormpath::Rails.config.web.id_site.enabled - get '/id_site_result' => actions['id_site_login#create'], as: :id_site_result - get '/logout_id_site' => actions['id_site_logout#create'], as: :logout_id_site + get '/id_site_result' => actions['id_site_login#new'], as: :id_site_result + get '/logout_id_site' => actions['id_site_logout#new'], as: :logout_id_site end end end diff --git a/spec/requests/id_site_login/get_spec.rb b/spec/requests/id_site_login/get_spec.rb new file mode 100644 index 0000000..e96b182 --- /dev/null +++ b/spec/requests/id_site_login/get_spec.rb @@ -0,0 +1,116 @@ +require 'spec_helper' + +describe 'IdSiteLogin GET', type: :request, vcr: true do + let(:application) { Stormpath::Rails::Client.application } + Timecop.freeze(Time.zone.now) do + let(:time) { Time.zone.now.to_i } + end + let(:cb_uri) { '/id_site_result' } + let(:path) { '' } + let(:tenant_name) { application.tenant.name } + let(:tenant_domain) { "https://#{tenant_name}.id.stormpath.io" } + let(:api_key_secret) { ENV['STORMPATH_API_KEY_SECRET'] } + let(:aud) { ENV['STORMPATH_API_KEY_ID'] } + let(:account) { application.accounts.create(account_attrs) } + let(:account_attrs) { FactoryGirl.attributes_for(:account) } + let(:jwt_response) do + JWT.encode( + { + 'iss' => tenant_domain, + 'sub' => account.href, + 'aud' => aud, + 'exp' => time + 1.minute.to_i, + 'iat' => time, + 'jti' => 'JX5HSMmEAevFBKJx4FfC3', + 'irt' => '5fbb73e7-f81b-41f2-8031-b08750da6298', + 'state' => '', + 'isNewSub' => false, + 'status' => 'AUTHENTICATED', + 'cb_uri' => 'http://localhost:3000/id_site_result' + }, + api_key_secret, + 'HS256' + ) + end + + before do + allow(web_config.id_site).to receive(:enabled).and_return(true) + Rails.application.reload_routes! + end + + after do + account.delete if account + allow(web_config.id_site).to receive(:enabled).and_return(false) + Rails.application.reload_routes! + end + + describe 'HTTP_ACCEPT=text/html' do + context 'successfull login' do + it 'should redirect' do + get '/id_site_result', jwtResponse: jwt_response + expect(response).to redirect_to('/') + expect(response.status).to eq(302) + end + end + + context 'invalid jwt' do + describe 'expired' do + let(:time) { Time.zone.now.to_i - 10.minutes } + + it 'should render flash error' do + get '/id_site_result', jwtResponse: jwt_response + expect(controller).to set_flash[:error].now + end + end + + describe 'bad signature' do + let(:api_key_secret) { 'badapikeysecret' } + + it 'should render flash error' do + get '/id_site_result', jwtResponse: jwt_response + expect(controller).to set_flash[:error].now + end + end + end + end + + describe 'application/json' do + let(:headers) do + { + 'ACCEPT' => 'application/json' + } + end + + context 'successfull login' do + it 'should respond with ok' do + get '/id_site_result', { jwtResponse: jwt_response }, headers + expect(response.status).to eq(200) + end + + it 'should respond with the logged in account' do + get '/id_site_result', { jwtResponse: jwt_response }, headers + expect(response.body).to include('account') + end + end + + context 'invalid jwt' do + describe 'expired' do + let(:time) { Time.zone.now.to_i - 10.minutes } + + it 'should respond with error message' do + get '/id_site_result', { jwtResponse: jwt_response }, headers + expect(response.body).to include('message', 'Signature has expired') + end + end + + describe 'bad signature' do + let(:api_key_secret) { 'badapikeysecret' } + + it 'should render flash error' do + get '/id_site_result', { jwtResponse: jwt_response }, headers + expect(response.body).to include('message', 'Signature verification raised') + end + end + end + end +end From c83a88c898986e9224bbbddd8d9af69755b930c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Thu, 29 Sep 2016 16:00:52 +0200 Subject: [PATCH 10/14] Fix failing travis spec --- spec/services/controller_authenticaton_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/controller_authenticaton_spec.rb b/spec/services/controller_authenticaton_spec.rb index a4644e2..c5eeb4d 100644 --- a/spec/services/controller_authenticaton_spec.rb +++ b/spec/services/controller_authenticaton_spec.rb @@ -536,7 +536,7 @@ end describe 'with un encoded api key and secret' do - let(:credentials) { "#{api_key.id}:#{api_key.secret}" } + let(:credentials) { 'unencodedapikeyid:unencodedapikeysecret' } it 'raises an UnauthenticatedRequest error' do expect do From 5d2b30ff2dfadf84b6618ecaa42b96e2435cd738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Tue, 25 Oct 2016 15:38:16 +0200 Subject: [PATCH 11/14] Use ID site auth logic from Ruby SDK and remove the PayloadBuilder --- .../rails/id_site_login/new_controller.rb | 10 +--- .../rails/id_site_logout/new_controller.rb | 7 ++- .../stormpath/rails/login/new_controller.rb | 7 ++- .../rails/register/new_controller.rb | 7 ++- .../stormpath/rails/payload_builder.rb | 38 ------------- spec/services/payload_builder_spec.rb | 56 ------------------- 6 files changed, 18 insertions(+), 107 deletions(-) delete mode 100644 app/services/stormpath/rails/payload_builder.rb delete mode 100644 spec/services/payload_builder_spec.rb diff --git a/app/controllers/stormpath/rails/id_site_login/new_controller.rb b/app/controllers/stormpath/rails/id_site_login/new_controller.rb index 4b269fa..b117c00 100644 --- a/app/controllers/stormpath/rails/id_site_login/new_controller.rb +++ b/app/controllers/stormpath/rails/id_site_login/new_controller.rb @@ -6,11 +6,11 @@ class NewController < BaseController def call begin - jwt = JWT.decode(params[:jwtResponse], ENV['STORMPATH_API_KEY_SECRET'], 'HS256') - account = Stormpath::Rails::Client.client.accounts.get(account_href(jwt)) + result = Stormpath::Rails::Client.application.handle_id_site_callback(request.url) + account = Stormpath::Rails::Client.client.accounts.get(result.account_href) login_the_account(account) respond_with_success(account) - rescue Stormpath::Error, JWT::VerificationError, JWT::ExpiredSignature => error + rescue Stormpath::Error => error respond_with_error(error) end end @@ -51,10 +51,6 @@ def login_redirect_route stormpath_config.web.login.next_uri end end - - def account_href(jwt) - jwt.first['sub'] - end end end end diff --git a/app/controllers/stormpath/rails/id_site_logout/new_controller.rb b/app/controllers/stormpath/rails/id_site_logout/new_controller.rb index a4d745f..91e21a0 100644 --- a/app/controllers/stormpath/rails/id_site_logout/new_controller.rb +++ b/app/controllers/stormpath/rails/id_site_logout/new_controller.rb @@ -3,9 +3,12 @@ module Rails module IdSiteLogout class NewController < BaseController def call - jwt = PayloadBuilder.new(:logout, cb_uri: root_url).jwt + callback_url = Stormpath::Rails::Client.application.create_id_site_url( + callback_uri: root_url, + logout: true + ) TokenAndCookiesCleaner.new(cookies).remove - redirect_to "https://api.stormpath.com/sso/logout?jwtRequest=#{jwt}" + redirect_to callback_url end end end diff --git a/app/controllers/stormpath/rails/login/new_controller.rb b/app/controllers/stormpath/rails/login/new_controller.rb index d2ea6a7..2d57bbf 100644 --- a/app/controllers/stormpath/rails/login/new_controller.rb +++ b/app/controllers/stormpath/rails/login/new_controller.rb @@ -6,8 +6,11 @@ class NewController < BaseController def call if stormpath_config.web.id_site.enabled - jwt = PayloadBuilder.new(:login, cb_uri: id_site_result_url).jwt - redirect_to "https://api.stormpath.com/sso?jwtRequest=#{jwt}" + callback_url = Stormpath::Rails::Client.application.create_id_site_url( + callback_uri: id_site_result_url, + path: Stormpath::Rails.config.web.id_site.login_uri + ) + redirect_to callback_url else respond_to do |format| format.json { render json: LoginNewSerializer.to_h } diff --git a/app/controllers/stormpath/rails/register/new_controller.rb b/app/controllers/stormpath/rails/register/new_controller.rb index a9afdb4..0bf4f6a 100644 --- a/app/controllers/stormpath/rails/register/new_controller.rb +++ b/app/controllers/stormpath/rails/register/new_controller.rb @@ -4,8 +4,11 @@ module Register class NewController < BaseController def call if stormpath_config.web.id_site.enabled - jwt = PayloadBuilder.new(:register, cb_uri: id_site_result_url).jwt - redirect_to "https://api.stormpath.com/sso?jwtRequest=#{jwt}" + callback_url = Stormpath::Rails::Client.application.create_id_site_url( + callback_uri: id_site_result_url, + path: Stormpath::Rails.config.web.id_site.register_uri + ) + redirect_to callback_url elsif signed_in? redirect_to root_path else diff --git a/app/services/stormpath/rails/payload_builder.rb b/app/services/stormpath/rails/payload_builder.rb deleted file mode 100644 index a8b9587..0000000 --- a/app/services/stormpath/rails/payload_builder.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Stormpath - module Rails - class PayloadBuilder - attr_reader :resource, :cb_uri - - def initialize(resource, options = {}) - @resource = resource - @cb_uri = options[:cb_uri] - end - - def jwt - JWT.encode(payload, ENV['STORMPATH_API_KEY_SECRET'], 'HS256') - end - - private - - def payload - { - 'iat' => Time.now.to_i, - 'iss' => ENV['STORMPATH_API_KEY_ID'], - 'sub' => ENV['STORMPATH_APPLICATION_URL'], - 'cb_uri' => cb_uri, - 'jti' => SecureRandom.uuid, - 'path' => path, - 'state' => '' - } - end - - def path - if resource == :register - Stormpath::Rails.config.web.id_site.register_uri - else - Stormpath::Rails.config.web.id_site.login_uri - end - end - end - end -end diff --git a/spec/services/payload_builder_spec.rb b/spec/services/payload_builder_spec.rb deleted file mode 100644 index c9331f5..0000000 --- a/spec/services/payload_builder_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' - -describe Stormpath::Rails::PayloadBuilder, type: :service do - Timecop.freeze(Time.zone.now) do - let(:time) { Time.zone.now.to_i } - end - - let(:jwt) do - JWT.encode( - { - 'iat' => time, - 'iss' => ENV['STORMPATH_API_KEY_ID'], - 'sub' => ENV['STORMPATH_APPLICATION_URL'], - 'cb_uri' => cb_uri, - 'jti' => 'secure_random_uuid', - 'path' => path, - 'state' => '' - }, - ENV['STORMPATH_API_KEY_SECRET'], - 'HS256' - ) - end - - before do - allow(SecureRandom).to receive(:uuid).and_return('secure_random_uuid') - allow(web_config.id_site).to receive(:enabled).and_return(true) - Rails.application.reload_routes! - end - - context 'login id site' do - let(:cb_uri) { id_site_result_url } - let(:path) { '' } - - it 'should return correct jwt for login id site' do - expect(Stormpath::Rails::PayloadBuilder.new(:login, cb_uri: id_site_result_url).jwt).to eq jwt - end - end - - context 'when logout id site' do - let(:cb_uri) { root_url } - let(:path) { '' } - - it 'should return correct jwt for logout id site' do - expect(Stormpath::Rails::PayloadBuilder.new(:logout, cb_uri: root_url).jwt).to eq jwt - end - end - - context 'when register id site' do - let(:cb_uri) { id_site_result_url } - let(:path) { '/#/register' } - - it 'should return correct jwt' do - expect(Stormpath::Rails::PayloadBuilder.new(:register, cb_uri: id_site_result_url).jwt).to eq jwt - end - end -end From bc537343845bf053253a4ff5227a0cff4ec371c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Tue, 25 Oct 2016 16:00:28 +0200 Subject: [PATCH 12/14] Rescue JWT expiration error and display message --- .../stormpath/rails/id_site_login/new_controller.rb | 2 +- spec/requests/id_site_login/get_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/stormpath/rails/id_site_login/new_controller.rb b/app/controllers/stormpath/rails/id_site_login/new_controller.rb index b117c00..fe5188f 100644 --- a/app/controllers/stormpath/rails/id_site_login/new_controller.rb +++ b/app/controllers/stormpath/rails/id_site_login/new_controller.rb @@ -10,7 +10,7 @@ def call account = Stormpath::Rails::Client.client.accounts.get(result.account_href) login_the_account(account) respond_with_success(account) - rescue Stormpath::Error => error + rescue Stormpath::Error, JWT::VerificationError => error respond_with_error(error) end end diff --git a/spec/requests/id_site_login/get_spec.rb b/spec/requests/id_site_login/get_spec.rb index e96b182..0b494fc 100644 --- a/spec/requests/id_site_login/get_spec.rb +++ b/spec/requests/id_site_login/get_spec.rb @@ -97,9 +97,9 @@ describe 'expired' do let(:time) { Time.zone.now.to_i - 10.minutes } - it 'should respond with error message' do + it 'should raise error' do get '/id_site_result', { jwtResponse: jwt_response }, headers - expect(response.body).to include('message', 'Signature has expired') + expect(response.body).to include('message', 'Token is invalid') end end From 4c3c5fc1906670736478cf192efedea3a6815693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Wed, 2 Nov 2016 13:00:38 +0100 Subject: [PATCH 13/14] Move callback_url generation into method and write specs for token and cookies cleaner --- .../rails/id_site_logout/new_controller.rb | 11 +++-- .../stormpath/rails/login/new_controller.rb | 13 ++++-- .../rails/register/new_controller.rb | 13 ++++-- spec/services/delete_refresh_token_spec.rb | 2 +- .../token_and_cookies_cleaner_spec.rb | 45 +++++++++++++++++++ 5 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 spec/services/token_and_cookies_cleaner_spec.rb diff --git a/app/controllers/stormpath/rails/id_site_logout/new_controller.rb b/app/controllers/stormpath/rails/id_site_logout/new_controller.rb index 91e21a0..f7e6b0e 100644 --- a/app/controllers/stormpath/rails/id_site_logout/new_controller.rb +++ b/app/controllers/stormpath/rails/id_site_logout/new_controller.rb @@ -3,13 +3,16 @@ module Rails module IdSiteLogout class NewController < BaseController def call - callback_url = Stormpath::Rails::Client.application.create_id_site_url( - callback_uri: root_url, - logout: true - ) TokenAndCookiesCleaner.new(cookies).remove redirect_to callback_url end + + private + + def callback_url + Stormpath::Rails::Client.application.create_id_site_url(callback_uri: root_url, + logout: true) + end end end end diff --git a/app/controllers/stormpath/rails/login/new_controller.rb b/app/controllers/stormpath/rails/login/new_controller.rb index 2d57bbf..0a4f1d3 100644 --- a/app/controllers/stormpath/rails/login/new_controller.rb +++ b/app/controllers/stormpath/rails/login/new_controller.rb @@ -6,10 +6,6 @@ class NewController < BaseController def call if stormpath_config.web.id_site.enabled - callback_url = Stormpath::Rails::Client.application.create_id_site_url( - callback_uri: id_site_result_url, - path: Stormpath::Rails.config.web.id_site.login_uri - ) redirect_to callback_url else respond_to do |format| @@ -18,6 +14,15 @@ def call end end end + + private + + def callback_url + Stormpath::Rails::Client.application.create_id_site_url( + callback_uri: id_site_result_url, + path: Stormpath::Rails.config.web.id_site.login_uri + ) + end end end end diff --git a/app/controllers/stormpath/rails/register/new_controller.rb b/app/controllers/stormpath/rails/register/new_controller.rb index 0bf4f6a..d5fbb12 100644 --- a/app/controllers/stormpath/rails/register/new_controller.rb +++ b/app/controllers/stormpath/rails/register/new_controller.rb @@ -4,10 +4,6 @@ module Register class NewController < BaseController def call if stormpath_config.web.id_site.enabled - callback_url = Stormpath::Rails::Client.application.create_id_site_url( - callback_uri: id_site_result_url, - path: Stormpath::Rails.config.web.id_site.register_uri - ) redirect_to callback_url elsif signed_in? redirect_to root_path @@ -18,6 +14,15 @@ def call end end end + + private + + def callback_url + Stormpath::Rails::Client.application.create_id_site_url( + callback_uri: id_site_result_url, + path: Stormpath::Rails.config.web.id_site.register_uri + ) + end end end end diff --git a/spec/services/delete_refresh_token_spec.rb b/spec/services/delete_refresh_token_spec.rb index f2ee787..0a22023 100644 --- a/spec/services/delete_refresh_token_spec.rb +++ b/spec/services/delete_refresh_token_spec.rb @@ -24,7 +24,7 @@ after { delete_test_account } - it 'deletes the access token' do + it 'deletes the refresh token' do expect do Stormpath::Rails::DeleteRefreshToken.new(refresh_token).call end.to change { account.refresh_tokens.count }.from(1).to(0) diff --git a/spec/services/token_and_cookies_cleaner_spec.rb b/spec/services/token_and_cookies_cleaner_spec.rb new file mode 100644 index 0000000..dd6e608 --- /dev/null +++ b/spec/services/token_and_cookies_cleaner_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe Stormpath::Rails::TokenAndCookiesCleaner, vcr: true, type: :service do + let(:account) { create_test_account } + + let(:password_grant_request) do + Stormpath::Oauth::PasswordGrantRequest.new(account.email, 'Password1337') + end + + let(:application) { Stormpath::Rails::Client.application } + + let(:access_token_authentication_result) do + application.authenticate_oauth(password_grant_request) + end + + let(:access_token) { access_token_authentication_result.access_token } + + let(:refresh_token) { access_token_authentication_result.refresh_token } + + let(:mocked_cookies_session) do + { + 'access_token' => access_token, + 'refresh_token' => refresh_token + } + end + + before do + account + access_token_authentication_result + end + + after { delete_test_account } + + it 'deletes the access token' do + expect do + Stormpath::Rails::TokenAndCookiesCleaner.new(mocked_cookies_session).remove + end.to change { account.access_tokens.count }.from(1).to(0) + end + + it 'deletes the refresh token' do + expect do + Stormpath::Rails::TokenAndCookiesCleaner.new(mocked_cookies_session).remove + end.to change { account.refresh_tokens.count }.from(1).to(0) + end +end From ed60b552c6ee5917b8581d8b21dd4ed0cdb28ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20=C4=86ilimkovi=C4=87?= Date: Wed, 2 Nov 2016 13:48:20 +0100 Subject: [PATCH 14/14] Randomize emails in factories even more --- spec/factories.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/factories.rb b/spec/factories.rb index ba421d8..9e3d613 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :account, class: Stormpath::Resource::Account do - sequence(:email) { |n| "dev#{n}@example.com" } + sequence(:email) { |n| "dev-#{n}-#{Faker::Lorem.word}@example.com" } password 'Password1337' given_name { Faker::Name.first_name } surname { Faker::Name.last_name }