From 8d8faf684c07bffd9a69f87e4fa4003663745f60 Mon Sep 17 00:00:00 2001 From: augier Date: Sun, 13 Sep 2015 14:07:50 -0700 Subject: [PATCH 001/105] OpenID Connect debut work --- Gemfile | 3 + .../openid/authorizations_controller.rb | 47 +++++++++++ app/controllers/openid/connect_controller.rb | 4 + .../openid/discovery_controller.rb | 45 ++++++++++ config/routes.rb | 9 ++ db/schema.rb | 2 +- lib/openid_connect/authorization_endpoint.rb | 82 +++++++++++++++++++ lib/openid_connect/token_endpoint.rb | 29 +++++++ 8 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 app/controllers/openid/authorizations_controller.rb create mode 100644 app/controllers/openid/connect_controller.rb create mode 100644 app/controllers/openid/discovery_controller.rb create mode 100644 lib/openid_connect/authorization_endpoint.rb create mode 100644 lib/openid_connect/token_endpoint.rb diff --git a/Gemfile b/Gemfile index 67cf53b1a..2e4c4122e 100644 --- a/Gemfile +++ b/Gemfile @@ -149,6 +149,9 @@ gem "omniauth-twitter", "1.2.1" gem "twitter", "5.15.0" gem "omniauth-wordpress", "0.2.2" +# OpenID Connect +gem "openid_connect" + # Serializers gem "active_model_serializers", "0.9.3" diff --git a/app/controllers/openid/authorizations_controller.rb b/app/controllers/openid/authorizations_controller.rb new file mode 100644 index 000000000..f1d407043 --- /dev/null +++ b/app/controllers/openid/authorizations_controller.rb @@ -0,0 +1,47 @@ +class AuthorizationsController < ApplicationController + rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| + @error = e + logger.info e.backtrace[0,10].join("\n") + render :error, status: e.status + end + + def new + call_authorization_endpoint + end + + def create + call_authorization_endpoint :allow_approval, params[:approve] + end + + private + + def call_authorization_endpoint(allow_approval = false, approved = false) + endpoint = AuthorizationEndpoint.new allow_approval, approved + rack_response = *endpoint.call(request.env) + @client, @response_type, @redirect_uri, @scopes, @_request_, @request_uri, @request_object = *[ + endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint._request_, endpoint.request_uri, endpoint.request_object + ] + require_authentication + if ( + !allow_approval && + (max_age = @request_object.try(:id_token).try(:max_age)) && + current_account.last_logged_in_at < max_age.seconds.ago + ) + flash[:notice] = 'Exceeded Max Age, Login Again' + unauthenticate! + require_authentication + end + respond_as_rack_app *rack_response + end + + def respond_as_rack_app(status, header, response) + ["WWW-Authenticate"].each do |key| + headers[key] = header[key] if header[key].present? + end + if response.redirect? + redirect_to header['Location'] + else + render :new + end + end +end diff --git a/app/controllers/openid/connect_controller.rb b/app/controllers/openid/connect_controller.rb new file mode 100644 index 000000000..c552988a6 --- /dev/null +++ b/app/controllers/openid/connect_controller.rb @@ -0,0 +1,4 @@ +class ConnectController < ApplicationController + def show + end +end diff --git a/app/controllers/openid/discovery_controller.rb b/app/controllers/openid/discovery_controller.rb new file mode 100644 index 000000000..1b70dcae0 --- /dev/null +++ b/app/controllers/openid/discovery_controller.rb @@ -0,0 +1,45 @@ +class DiscoveryController < ApplicationController + def show + case params[:id] + when 'webfinger' + webfinger_discovery + when 'openid-configuration' + openid_configuration + else + raise HttpError::NotFound + end + end + + private + + def webfinger_discovery + jrd = { + links: [{ + rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, + href: "http://0.0.0.0:3000" + }] + } + jrd[:subject] = params[:resource] if params[:resource].present? + render json: jrd, content_type: "application/jrd+json" + end + + def openid_configuration + config = OpenIDConnect::Discovery::Provider::Config::Response.new( + issuer: "http://0.0.0.0:3000", + authorization_endpoint: "#{authorizations_url}/new", + token_endpoint: access_tokens_url, + userinfo_endpoint: user_info_url, + jwks_uri: "#{authorizations_url}/jwks.json", + registration_endpoint: "http://0.0.0.0:3000/connect", + scopes_supported: "iss", + response_types_supported: "Client.available_response_types", + grant_types_supported: "Client.available_grant_types", + request_object_signing_alg_values_supported: [:HS256, :HS384, :HS512], + subject_types_supported: ['public', 'pairwise'], + id_token_signing_alg_values_supported: [:RS256], + token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post'], + claims_supported: ['sub', 'iss', 'name', 'email'] + ) + render json: config + end +end diff --git a/config/routes.rb b/config/routes.rb index 4babaf630..147212a98 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -240,4 +240,13 @@ Diaspora::Application.routes.draw do # Startpage root :to => 'home#show' + + #OpenID Connect & OAuth + resource :openid do + resources :authorizations, only: [:new, :create] + match 'connect', to: 'connect#show', via: [:get, :post] + match '.well-known/:id', to: 'discovery#show' , :via => [:get, :post] + match 'user_info', to: 'user#show', :via => [:get, :post] + post 'access_tokens', to: proc { |env| TokenEndpoint.new.call(env) } + end end diff --git a/db/schema.rb b/db/schema.rb index 6a526fe46..59f51d5d1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -419,7 +419,7 @@ ActiveRecord::Schema.define(version: 20151003142048) do t.string "location", limit: 255 t.string "full_name", limit: 70 t.boolean "nsfw", default: false - t.boolean "public_details", default: false + t.boolean "public_details", default: false end add_index "profiles", ["full_name", "searchable"], name: "index_profiles_on_full_name_and_searchable", using: :btree diff --git a/lib/openid_connect/authorization_endpoint.rb b/lib/openid_connect/authorization_endpoint.rb new file mode 100644 index 000000000..0d8b021c3 --- /dev/null +++ b/lib/openid_connect/authorization_endpoint.rb @@ -0,0 +1,82 @@ +class AuthorizationEndpoint + attr_accessor :app, :account, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object + delegate :call, to: :app + + def initialize(allow_approval = false, approved = false) + @account = nil + @app = Rack::OAuth2::Server::Authorize.new do |req, res| + @client = nil # Find the client + res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@client.redirect_uris) + if res.protocol_params_location == :fragment && req.nonce.blank? + req.invalid_request! 'nonce required' + end + @scopes = req.scope.inject([]) do |_scopes_, scope| + _scopes_ << Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}" + end + @request_object = if (@_request_ = req.request).present? + OpenIDConnect::RequestObject.decode req.request, nil # @client.secret + elsif (@request_uri = req.request_uri).present? + OpenIDConnect::RequestObject.fetch req.request_uri, nil # @client.secret + end + if Client.available_response_types.include? Array(req.response_type).collect(&:to_s).join(' ') + if allow_approval + if approved + approved! req, res + else + req.access_denied! + end + else + @response_type = req.response_type + end + else + req.unsupported_response_type! + end + end + end + + def approved!(req, res) + response_types = Array(req.response_type) + if response_types.include? :code + authorization = account.authorizations.create!(client: @client, redirect_uri: res.redirect_uri, nonce: req.nonce) + authorization.scopes << scopes + if @request_object + authorization.create_authorization_request_object!( + request_object: RequestObject.new( + jwt_string: @request_object.to_jwt(@client.secret, :HS256) + ) + ) + end + res.code = authorization.code + end + if response_types.include? :token + access_token = account.access_tokens.create!(client: @client) + access_token.scopes << scopes + if @request_object + access_token.create_access_token_request_object!( + request_object: RequestObject.new( + jwt_string: @request_object.to_jwt(@client.secret, :HS256) + ) + ) + end + res.access_token = access_token.to_bearer_token + end + if response_types.include? :id_token + _id_token_ = account.id_tokens.create!( + client: @client, + nonce: req.nonce + ) + if @request_object + _id_token_.create_id_token_request_object!( + request_object: RequestObject.new( + jwt_string: @request_object.to_jwt(@client.secret, :HS256) + ) + ) + end + res.id_token = _id_token_.to_jwt( + code: (res.respond_to?(:code) ? res.code : nil), + access_token: (res.respond_to?(:access_token) ? res.access_token : nil) + ) + end + res.approve! + end +end \ No newline at end of file diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb new file mode 100644 index 000000000..c21984972 --- /dev/null +++ b/lib/openid_connect/token_endpoint.rb @@ -0,0 +1,29 @@ +class TokenEndpoint + attr_accessor :app + delegate :call, to: :app + + def initialize + @app = Rack::OAuth2::Server::Token.new do |req, res| + client = Client.find_by_identifier(req.client_id) || req.invalid_client! + client.secret == req.client_secret || req.invalid_client! + case req.grant_type + when :client_credentials + res.access_token = client.access_tokens.create!.to_bearer_token + when :authorization_code + authorization = client.authorizations.valid.find_by_code(req.code) + req.invalid_grant! if authorization.blank? || !authorization.valid_redirect_uri?(req.redirect_uri) + access_token = authorization.access_token + res.access_token = access_token.to_bearer_token + if access_token.accessible?(Scope::OPENID) + res.id_token = access_token.account.id_tokens.create!( + client: access_token.client, + nonce: authorization.nonce, + request_object: authorization.request_object + ).to_response_object.to_jwt IdToken.config[:private_key] + end + else + req.unsupported_grant_type! + end + end + end +end \ No newline at end of file From a1f3d5f5f9b9e2c54b6d03adb6dec40d9b958af5 Mon Sep 17 00:00:00 2001 From: Augier Date: Sun, 14 Jun 2015 16:13:16 +0200 Subject: [PATCH 002/105] Getting token from user credential flow --- app/controllers/openid/LICENSE | 22 ++++++ .../openid/discovery_controller.rb | 6 +- app/models/o_auth_application.rb | 6 ++ app/models/token.rb | 19 +++++ app/models/user.rb | 2 + config/application.rb | 1 + ...150613202109_create_o_auth_applications.rb | 10 +++ db/migrate/20150614134031_create_tokens.rb | 14 ++++ db/schema.rb | 15 ++++ lib/openid_connect/LICENSE | 22 ++++++ lib/openid_connect/authorization_endpoint.rb | 76 +------------------ lib/openid_connect/token_endpoint.rb | 25 +++--- .../lib/openid_connect/token_endpoint_spec.rb | 8 ++ spec/models/o_auth_application_spec.rb | 5 ++ spec/models/token_spec.rb | 5 ++ 15 files changed, 143 insertions(+), 93 deletions(-) create mode 100644 app/controllers/openid/LICENSE create mode 100644 app/models/o_auth_application.rb create mode 100644 app/models/token.rb create mode 100644 db/migrate/20150613202109_create_o_auth_applications.rb create mode 100644 db/migrate/20150614134031_create_tokens.rb create mode 100644 lib/openid_connect/LICENSE create mode 100644 spec/lib/openid_connect/token_endpoint_spec.rb create mode 100644 spec/models/o_auth_application_spec.rb create mode 100644 spec/models/token_spec.rb diff --git a/app/controllers/openid/LICENSE b/app/controllers/openid/LICENSE new file mode 100644 index 000000000..ace31447b --- /dev/null +++ b/app/controllers/openid/LICENSE @@ -0,0 +1,22 @@ +This code is based on https://github.com/nov/openid_connect_sample + +Copyright (c) 2011 nov matake + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/app/controllers/openid/discovery_controller.rb b/app/controllers/openid/discovery_controller.rb index 1b70dcae0..5b53e5c13 100644 --- a/app/controllers/openid/discovery_controller.rb +++ b/app/controllers/openid/discovery_controller.rb @@ -16,7 +16,7 @@ class DiscoveryController < ApplicationController jrd = { links: [{ rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, - href: "http://0.0.0.0:3000" + href: root_path }] } jrd[:subject] = params[:resource] if params[:resource].present? @@ -25,12 +25,12 @@ class DiscoveryController < ApplicationController def openid_configuration config = OpenIDConnect::Discovery::Provider::Config::Response.new( - issuer: "http://0.0.0.0:3000", + issuer: root_path, authorization_endpoint: "#{authorizations_url}/new", token_endpoint: access_tokens_url, userinfo_endpoint: user_info_url, jwks_uri: "#{authorizations_url}/jwks.json", - registration_endpoint: "http://0.0.0.0:3000/connect", + registration_endpoint: "#{root_path}/connect", scopes_supported: "iss", response_types_supported: "Client.available_response_types", grant_types_supported: "Client.available_grant_types", diff --git a/app/models/o_auth_application.rb b/app/models/o_auth_application.rb new file mode 100644 index 000000000..29d3cd87e --- /dev/null +++ b/app/models/o_auth_application.rb @@ -0,0 +1,6 @@ +class OAuthApplication < ActiveRecord::Base + validates :client_id, presence: true, uniqueness: true + validates :client_secret, presence: true + + has_many :tokens +end diff --git a/app/models/token.rb b/app/models/token.rb new file mode 100644 index 000000000..a6c50e814 --- /dev/null +++ b/app/models/token.rb @@ -0,0 +1,19 @@ +class Token < ActiveRecord::Base + belongs_to :o_auth_application + + before_validation :setup, on: :create + + validates :token, presence: true, uniqueness: true + + def setup + self.token = SecureRandom.hex(32) + self.expires_at = 24.hours.from_now + end + + def bearer_token + @bearer_token ||= Rack::OAuth2::AccessToken::Bearer.new( + access_token: token, + expires_in: (expires_at - Time.now.utc).to_i + ) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index cc0dd2331..1a8ebf3f4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -76,6 +76,8 @@ class User < ActiveRecord::Base has_many :reports + has_many :o_auth_applications + before_save :guard_unconfirmed_email, :save_person! diff --git a/config/application.rb b/config/application.rb index 47d39fcad..6c650594b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,6 +31,7 @@ module Diaspora # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += %W{#{config.root}/app} config.autoload_once_paths += %W{#{config.root}/lib} + config.autoload_paths += %W{#{config.root}/lib/openid_connect} # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb new file mode 100644 index 000000000..1ad4638d2 --- /dev/null +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -0,0 +1,10 @@ +class CreateOAuthApplications < ActiveRecord::Migration + def change + create_table :o_auth_applications do |t| + t.string :client_id + t.string :client_secret + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150614134031_create_tokens.rb b/db/migrate/20150614134031_create_tokens.rb new file mode 100644 index 000000000..0d6a0cc03 --- /dev/null +++ b/db/migrate/20150614134031_create_tokens.rb @@ -0,0 +1,14 @@ +class CreateTokens < ActiveRecord::Migration + def change + create_table :tokens do |t| + t.belongs_to :o_auth_application + t.string :token + t.datetime :expires_at + t.timestamps null: false + end + end + + def self.down + drop_table :tokens + end +end diff --git a/db/schema.rb b/db/schema.rb index 59f51d5d1..77fd2e4c4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -236,6 +236,13 @@ ActiveRecord::Schema.define(version: 20151003142048) do add_index "notifications", ["target_id"], name: "index_notifications_on_target_id", using: :btree add_index "notifications", ["target_type", "target_id"], name: "index_notifications_on_target_type_and_target_id", length: {"target_type"=>190, "target_id"=>nil}, using: :btree + create_table "o_auth_applications", force: :cascade do |t| + t.string "client_id", limit: 255 + t.string "client_secret", limit: 255 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "o_embed_caches", force: :cascade do |t| t.string "url", limit: 1024, null: false t.text "data", limit: 65535, null: false @@ -528,6 +535,14 @@ ActiveRecord::Schema.define(version: 20151003142048) do add_index "tags", ["name"], name: "index_tags_on_name", unique: true, length: {"name"=>191}, using: :btree + create_table "tokens", force: :cascade do |t| + t.integer "o_auth_application_id", limit: 4 + t.string "token", limit: 255 + t.datetime "expires_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "user_preferences", force: :cascade do |t| t.string "email_type", limit: 255 t.integer "user_id", limit: 4 diff --git a/lib/openid_connect/LICENSE b/lib/openid_connect/LICENSE new file mode 100644 index 000000000..ace31447b --- /dev/null +++ b/lib/openid_connect/LICENSE @@ -0,0 +1,22 @@ +This code is based on https://github.com/nov/openid_connect_sample + +Copyright (c) 2011 nov matake + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/openid_connect/authorization_endpoint.rb b/lib/openid_connect/authorization_endpoint.rb index 0d8b021c3..b584b2f28 100644 --- a/lib/openid_connect/authorization_endpoint.rb +++ b/lib/openid_connect/authorization_endpoint.rb @@ -3,80 +3,8 @@ class AuthorizationEndpoint delegate :call, to: :app def initialize(allow_approval = false, approved = false) - @account = nil @app = Rack::OAuth2::Server::Authorize.new do |req, res| - @client = nil # Find the client - res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@client.redirect_uris) - if res.protocol_params_location == :fragment && req.nonce.blank? - req.invalid_request! 'nonce required' - end - @scopes = req.scope.inject([]) do |_scopes_, scope| - _scopes_ << Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}" - end - @request_object = if (@_request_ = req.request).present? - OpenIDConnect::RequestObject.decode req.request, nil # @client.secret - elsif (@request_uri = req.request_uri).present? - OpenIDConnect::RequestObject.fetch req.request_uri, nil # @client.secret - end - if Client.available_response_types.include? Array(req.response_type).collect(&:to_s).join(' ') - if allow_approval - if approved - approved! req, res - else - req.access_denied! - end - else - @response_type = req.response_type - end - else - req.unsupported_response_type! - end + req.unsupported_response_type! # Not supported yet end end - - def approved!(req, res) - response_types = Array(req.response_type) - if response_types.include? :code - authorization = account.authorizations.create!(client: @client, redirect_uri: res.redirect_uri, nonce: req.nonce) - authorization.scopes << scopes - if @request_object - authorization.create_authorization_request_object!( - request_object: RequestObject.new( - jwt_string: @request_object.to_jwt(@client.secret, :HS256) - ) - ) - end - res.code = authorization.code - end - if response_types.include? :token - access_token = account.access_tokens.create!(client: @client) - access_token.scopes << scopes - if @request_object - access_token.create_access_token_request_object!( - request_object: RequestObject.new( - jwt_string: @request_object.to_jwt(@client.secret, :HS256) - ) - ) - end - res.access_token = access_token.to_bearer_token - end - if response_types.include? :id_token - _id_token_ = account.id_tokens.create!( - client: @client, - nonce: req.nonce - ) - if @request_object - _id_token_.create_id_token_request_object!( - request_object: RequestObject.new( - jwt_string: @request_object.to_jwt(@client.secret, :HS256) - ) - ) - end - res.id_token = _id_token_.to_jwt( - code: (res.respond_to?(:code) ? res.code : nil), - access_token: (res.respond_to?(:access_token) ? res.access_token : nil) - ) - end - res.approve! - end -end \ No newline at end of file +end diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb index c21984972..d3f5d8d72 100644 --- a/lib/openid_connect/token_endpoint.rb +++ b/lib/openid_connect/token_endpoint.rb @@ -4,26 +4,19 @@ class TokenEndpoint def initialize @app = Rack::OAuth2::Server::Token.new do |req, res| - client = Client.find_by_identifier(req.client_id) || req.invalid_client! - client.secret == req.client_secret || req.invalid_client! case req.grant_type - when :client_credentials - res.access_token = client.access_tokens.create!.to_bearer_token - when :authorization_code - authorization = client.authorizations.valid.find_by_code(req.code) - req.invalid_grant! if authorization.blank? || !authorization.valid_redirect_uri?(req.redirect_uri) - access_token = authorization.access_token - res.access_token = access_token.to_bearer_token - if access_token.accessible?(Scope::OPENID) - res.id_token = access_token.account.id_tokens.create!( - client: access_token.client, - nonce: authorization.nonce, - request_object: authorization.request_object - ).to_response_object.to_jwt IdToken.config[:private_key] + when :password + # If the grant type is password, the application does not have to be known + # If it does not exist, insert into DB + user = User.find_for_database_authentication(username: req.username) + o_auth_app = OAuthApplication.find_by_client_id req.client_id + o_auth_app ||= OAuthApplication.create!(client_id: req.client_id, client_secret: req.client_secret) + if user.valid_password? req.password + res.access_token = o_auth_app.tokens.create!.bearer_token end else req.unsupported_grant_type! end end end -end \ No newline at end of file +end diff --git a/spec/lib/openid_connect/token_endpoint_spec.rb b/spec/lib/openid_connect/token_endpoint_spec.rb new file mode 100644 index 000000000..44e6a26b1 --- /dev/null +++ b/spec/lib/openid_connect/token_endpoint_spec.rb @@ -0,0 +1,8 @@ +require 'rspec' + +describe TokenEndpoint do + + it "shoud generate a token" do + + end +end diff --git a/spec/models/o_auth_application_spec.rb b/spec/models/o_auth_application_spec.rb new file mode 100644 index 000000000..1f0bb9ef7 --- /dev/null +++ b/spec/models/o_auth_application_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe OAuthApplication, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/token_spec.rb b/spec/models/token_spec.rb new file mode 100644 index 000000000..18bba17d4 --- /dev/null +++ b/spec/models/token_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Token, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From efdfe318fd511af63c5a4652e0345f54c23ac7fd Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 13 Sep 2015 14:10:52 -0700 Subject: [PATCH 003/105] Add ability to get user info from access tokens --- Gemfile | 3 + Gemfile.lock | 56 ++++++++++++++++ app/controllers/users_controller.rb | 10 ++- app/models/token.rb | 8 ++- config/application.rb | 6 +- config/routes.rb | 4 +- db/migrate/20150614134031_create_tokens.rb | 2 +- features/desktop/protected_resource.feature | 31 +++++++++ features/step_definitions/openid_steps.rb | 37 +++++++++++ features/support/env.rb | 3 + lib/{openid_connect => openid}/LICENSE | 0 lib/openid/authentication.rb | 30 +++++++++ lib/openid/authorization_endpoint.rb | 12 ++++ lib/openid/token_endpoint.rb | 35 ++++++++++ lib/openid_connect/authorization_endpoint.rb | 10 --- lib/openid_connect/token_endpoint.rb | 22 ------- .../integration/openid/token_endpoint_spec.rb | 64 +++++++++++++++++++ .../lib/openid_connect/token_endpoint_spec.rb | 8 --- 18 files changed, 295 insertions(+), 46 deletions(-) create mode 100644 features/desktop/protected_resource.feature create mode 100644 features/step_definitions/openid_steps.rb rename lib/{openid_connect => openid}/LICENSE (100%) create mode 100644 lib/openid/authentication.rb create mode 100644 lib/openid/authorization_endpoint.rb create mode 100644 lib/openid/token_endpoint.rb delete mode 100644 lib/openid_connect/authorization_endpoint.rb delete mode 100644 lib/openid_connect/token_endpoint.rb create mode 100644 spec/integration/openid/token_endpoint_spec.rb delete mode 100644 spec/lib/openid_connect/token_endpoint_spec.rb diff --git a/Gemfile b/Gemfile index 2e4c4122e..72d743af0 100644 --- a/Gemfile +++ b/Gemfile @@ -279,6 +279,9 @@ group :test do gem "database_cleaner" , "1.5.1" gem "selenium-webdriver", "2.47.1" + gem "cucumber-api-steps", "0.13", require: false + gem "json_spec", "1.1.4" + # General helpers gem "factory_girl_rails", "4.5.0" diff --git a/Gemfile.lock b/Gemfile.lock index 7d873fb3b..ad32b01d8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -57,6 +57,7 @@ GEM ast (2.2.0) astrolabe (1.3.1) parser (~> 2.2) + attr_required (1.0.0) autoprefixer-rails (6.2.2) execjs json @@ -66,6 +67,7 @@ GEM jquery-rails railties bcrypt (3.1.10) + bindata (2.1.0) bootstrap-sass (3.3.6) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) @@ -126,6 +128,10 @@ GEM gherkin (~> 2.12) multi_json (>= 1.7.5, < 2.0) multi_test (>= 0.1.2) + cucumber-api-steps (0.13) + cucumber (>= 1.2.1) + jsonpath (>= 0.1.2) + rspec (>= 2.12.0) cucumber-rails (1.4.2) capybara (>= 1.1.2, < 3) cucumber (>= 1.3.8, < 2) @@ -390,6 +396,7 @@ GEM httparty (0.13.7) json (~> 1.8) multi_xml (>= 0.5.2) + httpclient (2.7.1) i18n (0.7.0) i18n-inflector (2.6.7) i18n (>= 0.4.1) @@ -423,8 +430,19 @@ GEM multi_json (>= 1.3) rake json (1.8.3) + json-jwt (1.5.1) + activesupport + bindata + multi_json (>= 1.3) + securecompare + url_safe_base64 json-schema (2.5.2) addressable (~> 2.3.8) + json_spec (1.1.4) + multi_json (~> 1.0) + rspec (>= 2.0, < 4.0) + jsonpath (0.5.7) + multi_json jwt (1.5.2) kaminari (0.16.3) actionpack (>= 3.0.0) @@ -504,6 +522,17 @@ GEM open_graph_reader (0.6.1) faraday (~> 0.9.0) nokogiri (~> 1.6) + openid_connect (0.9.2) + activemodel + attr_required (>= 1.0.0) + json (>= 1.4.3) + json-jwt (>= 1.5.0) + rack-oauth2 (>= 1.2.1) + swd (>= 1.0.0) + tzinfo + validate_email + validate_url + webfinger (>= 1.0.1) orm_adapter (0.5.0) parser (2.2.3.0) ast (>= 1.1, < 3.0) @@ -545,6 +574,12 @@ GEM activesupport rack-mobile-detect (0.4.0) rack + rack-oauth2 (1.2.1) + activesupport (>= 2.3) + attr_required (>= 0.0.5) + httpclient (>= 2.4) + multi_json (>= 1.3.6) + rack (>= 1.1) rack-piwik (0.3.0) rack-pjax (0.8.0) nokogiri (~> 1.5) @@ -708,6 +743,7 @@ GEM scss_lint (0.42.2) rainbow (~> 2.0) sass (~> 3.4.15) + securecompare (1.0.0) selenium-webdriver (2.47.1) childprocess (~> 0.5) multi_json (~> 1.0) @@ -757,6 +793,12 @@ GEM activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) state_machine (1.2.0) + swd (1.0.0) + activesupport (>= 3) + attr_required (>= 0.0.5) + httpclient (>= 2.4) + i18n + json (>= 1.4.3) sysexits (1.2.0) systemu (2.6.5) terminal-table (1.5.2) @@ -797,11 +839,22 @@ GEM kgio (~> 2.6) rack raindrops (~> 0.7) + url_safe_base64 (0.2.2) uuid (2.3.8) macaddr (~> 1.0) valid (1.1.0) + validate_email (0.1.6) + activemodel (>= 3.0) + mail (>= 2.2.5) + validate_url (1.0.2) + activemodel (>= 3.0.0) + addressable warden (1.2.4) rack (>= 1.0) + webfinger (1.0.1) + activesupport + httpclient (>= 2.4) + multi_json webmock (1.22.3) addressable (>= 2.3.6) crack (>= 0.3.2) @@ -830,6 +883,7 @@ DEPENDENCIES carrierwave (= 0.10.0) compass-rails (= 2.0.5) configurate (= 0.3.1) + cucumber-api-steps (= 0.13) cucumber-rails (= 1.4.2) database_cleaner (= 1.5.1) devise (= 3.5.3) @@ -867,6 +921,7 @@ DEPENDENCIES jshintrb (= 0.3.0) json (= 1.8.3) json-schema (= 2.5.2) + json_spec (= 1.1.4) leaflet-rails (= 0.7.4) logging-rails (= 0.5.0) markerb (= 1.1.0) @@ -882,6 +937,7 @@ DEPENDENCIES omniauth-twitter (= 1.2.1) omniauth-wordpress (= 0.2.2) open_graph_reader (= 0.6.1) + openid_connect pg (= 0.18.4) pronto (= 0.5.3) pronto-haml (= 0.5.0) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f929728ae..823f2ba3e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,9 +3,17 @@ # the COPYRIGHT file. class UsersController < ApplicationController - before_action :authenticate_user!, :except => [:new, :create, :public, :user_photo] + include Openid::Authentication + + before_action :authenticate_user!, except: [:new, :create, :public, :user_photo] + before_filter :require_access_token, only: [:show] respond_to :html + # TODO: Adjust so that it sends back only required elements, e.g, should not send hashed password (serialized_private_key) back + def show + render json: current_user + end + def edit @aspect = :user_edit @user = current_user diff --git a/app/models/token.rb b/app/models/token.rb index a6c50e814..37849715a 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -5,8 +5,10 @@ class Token < ActiveRecord::Base validates :token, presence: true, uniqueness: true + scope :valid, ->(time) { where("expires_at >= ?", time) } + def setup - self.token = SecureRandom.hex(32) + self.token = SecureRandom.hex(32) self.expires_at = 24.hours.from_now end @@ -16,4 +18,8 @@ class Token < ActiveRecord::Base expires_in: (expires_at - Time.now.utc).to_i ) end + + def accessible?(_scopes_or_claims_ = nil) + true # TODO: For now don't support scopes + end end diff --git a/config/application.rb b/config/application.rb index 6c650594b..acb8eba06 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,7 +31,7 @@ module Diaspora # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += %W{#{config.root}/app} config.autoload_once_paths += %W{#{config.root}/lib} - config.autoload_paths += %W{#{config.root}/lib/openid_connect} + config.autoload_paths += %W{#{config.root}/lib/openid} # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. @@ -108,5 +108,9 @@ module Diaspora host: AppConfig.pod_uri.authority } config.action_mailer.asset_host = AppConfig.pod_uri.to_s + + config.middleware.use Rack::OAuth2::Server::Resource::Bearer, 'OpenID Connect' do |req| + Token.valid(Time.now.utc).find_by(token: req.access_token) || req.invalid_token! + end end end diff --git a/config/routes.rb b/config/routes.rb index 147212a98..8dfbfd08a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -246,7 +246,7 @@ Diaspora::Application.routes.draw do resources :authorizations, only: [:new, :create] match 'connect', to: 'connect#show', via: [:get, :post] match '.well-known/:id', to: 'discovery#show' , :via => [:get, :post] - match 'user_info', to: 'user#show', :via => [:get, :post] - post 'access_tokens', to: proc { |env| TokenEndpoint.new.call(env) } + post 'access_tokens', to: proc { |env| Openid::TokenEndpoint.new.call(env) } + match 'user_info', to: 'users#show', :via => [:get, :post] end end diff --git a/db/migrate/20150614134031_create_tokens.rb b/db/migrate/20150614134031_create_tokens.rb index 0d6a0cc03..613a7f53d 100644 --- a/db/migrate/20150614134031_create_tokens.rb +++ b/db/migrate/20150614134031_create_tokens.rb @@ -1,5 +1,5 @@ class CreateTokens < ActiveRecord::Migration - def change + def self.up create_table :tokens do |t| t.belongs_to :o_auth_application t.string :token diff --git a/features/desktop/protected_resource.feature b/features/desktop/protected_resource.feature new file mode 100644 index 000000000..4d0cee1ec --- /dev/null +++ b/features/desktop/protected_resource.feature @@ -0,0 +1,31 @@ +@javascript +# TODO: Add tests for expired access tokens +# TODO: Add tests to check for WWW-Authenticate response header field as according to RFC 6750 +Feature: Access protected resources using bearer access token + Background: + Given a user with username "bob" + And I log in manually as "bob" with password "password" + And I send a post request to the token endpoint using "bob"'s credentials + + Scenario: Valid bearer tokens sent via Authorization Request Header Field + # TODO: Add tests + + Scenario: Valid bearer tokens sent via Form Encoded Parameter + # TODO: Add tests + + Scenario: Valid bearer tokens sent via URI query parameter + When I use received valid bearer tokens to access user info via URI query parameter + Then I should receive "bob"'s id, username, and email + # TODO: I want to confirm that the cache-control header in the response is private as according to RFC 6750 + # Unfortunately, selenium doesn't allow access to response headers + + Scenario: Invalid bearer tokens sent via URI query parameter + When I use invalid bearer tokens to access user info via URI query parameter + Then I should receive an "invalid_token" error + + Scenario: Valid bearer tokens sent via URI query parameter but user is logged out + When I log out manually + And I use received valid bearer tokens to access user info via URI query parameter + Then I should see "Sign in" in the content + When I log in manually as "bob" with password "password" + Then I should receive "bob"'s id, username, and email diff --git a/features/step_definitions/openid_steps.rb b/features/step_definitions/openid_steps.rb new file mode 100644 index 000000000..180711c35 --- /dev/null +++ b/features/step_definitions/openid_steps.rb @@ -0,0 +1,37 @@ +# Password has been hard coded as all test accounts seem to have a password of "password" +Given /^I send a post request to the token endpoint using "([^\"]*)"'s credentials$/ do |username| + user = User.find_by(username: username) + tokenEndpointURL = "/openid/access_tokens" + tokenEndpointURLQuery = "?grant_type=password&username=" + + user.username + + "&password=password&client_id=4&client_secret=azerty" + post tokenEndpointURL + tokenEndpointURLQuery +end + +When /^I use received valid bearer tokens to access user info via URI query parameter$/ do + accessTokenJson = JSON.parse(last_response.body) + userInfoEndPointURL = "/openid/user_info/" + userInfoEndPointURLQuery = "?access_token=" + accessTokenJson["access_token"] + visit userInfoEndPointURL + userInfoEndPointURLQuery +end + +When /^I use invalid bearer tokens to access user info via URI query parameter$/ do + userInfoEndPointURL = "/openid/user_info/" + userInfoEndPointURLQuery = "?access_token=" + SecureRandom.hex(32) + visit userInfoEndPointURL + userInfoEndPointURLQuery +end + +Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username| + user = User.find_by_username(username) + expect(page).to have_content(user.username) + expect(page).to have_content(user.language) + expect(page).to have_content(user.email) +end + +Then /^I should receive an "([^\"]*)" error$/ do |error_message| + expect(page).to have_content(error_message) +end + +Then /^I should see "([^\"]*)" in the content$/ do |content| + expect(page).to have_content(content) +end diff --git a/features/support/env.rb b/features/support/env.rb index 888ef0191..07dc02d0d 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -12,6 +12,9 @@ require "capybara/cucumber" require "capybara/session" require "selenium/webdriver" +require "cucumber/api_steps" +require "json_spec/cucumber" + # Ensure we know the appservers port Capybara.server_port = AppConfig.pod_uri.port Rails.application.routes.default_url_options[:host] = AppConfig.pod_uri.host diff --git a/lib/openid_connect/LICENSE b/lib/openid/LICENSE similarity index 100% rename from lib/openid_connect/LICENSE rename to lib/openid/LICENSE diff --git a/lib/openid/authentication.rb b/lib/openid/authentication.rb new file mode 100644 index 000000000..7423ba21e --- /dev/null +++ b/lib/openid/authentication.rb @@ -0,0 +1,30 @@ +module Openid + module Authentication + + def self.included(klass) + klass.send :include, Authentication::Helper + end + + module Helper + def current_token + @current_token + end + end + + def require_access_token + @current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN] + unless @current_token + raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user") + end + # TODO: This block is useless until we actually start checking for scopes + unless @current_token.try(:accessible?, required_scopes) + raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope) + end + end + + # Scopes should be implemented here + def required_scopes + nil # as default + end + end +end diff --git a/lib/openid/authorization_endpoint.rb b/lib/openid/authorization_endpoint.rb new file mode 100644 index 000000000..3b8ea9a55 --- /dev/null +++ b/lib/openid/authorization_endpoint.rb @@ -0,0 +1,12 @@ +module Openid + class AuthorizationEndpoint + attr_accessor :app, :account, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object + delegate :call, to: :app + + def initialize(allow_approval = false, approved = false) + @app = Rack::OAuth2::Server::Authorize.new do |req, res| + req.unsupported_response_type! # TODO: not supported yet + end + end + end +end diff --git a/lib/openid/token_endpoint.rb b/lib/openid/token_endpoint.rb new file mode 100644 index 000000000..ae52a0305 --- /dev/null +++ b/lib/openid/token_endpoint.rb @@ -0,0 +1,35 @@ +module Openid + class TokenEndpoint + attr_accessor :app + delegate :call, to: :app + + def initialize + @app = Rack::OAuth2::Server::Token.new do |req, res| + case req.grant_type + when :password + o_auth_app = retrieveOrCreateNewClientApplication(req) + user = User.find_for_database_authentication(username: req.username) + if o_auth_app && user && user.valid_password?(req.password) + res.access_token = o_auth_app.tokens.create!.bearer_token + else + req.invalid_grant! + end + else + res.unsupported_grant_type! + end + end + end + + def retrieveOrCreateNewClientApplication(req) + retrieveClient(req) || createClient(req) + end + + def retrieveClient(req) + OAuthApplication.find_by_client_id req.client_id + end + + def createClient(req) + OAuthApplication.create!(client_id: req.client_id, client_secret: req.client_secret) + end + end +end diff --git a/lib/openid_connect/authorization_endpoint.rb b/lib/openid_connect/authorization_endpoint.rb deleted file mode 100644 index b584b2f28..000000000 --- a/lib/openid_connect/authorization_endpoint.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AuthorizationEndpoint - attr_accessor :app, :account, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object - delegate :call, to: :app - - def initialize(allow_approval = false, approved = false) - @app = Rack::OAuth2::Server::Authorize.new do |req, res| - req.unsupported_response_type! # Not supported yet - end - end -end diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb deleted file mode 100644 index d3f5d8d72..000000000 --- a/lib/openid_connect/token_endpoint.rb +++ /dev/null @@ -1,22 +0,0 @@ -class TokenEndpoint - attr_accessor :app - delegate :call, to: :app - - def initialize - @app = Rack::OAuth2::Server::Token.new do |req, res| - case req.grant_type - when :password - # If the grant type is password, the application does not have to be known - # If it does not exist, insert into DB - user = User.find_for_database_authentication(username: req.username) - o_auth_app = OAuthApplication.find_by_client_id req.client_id - o_auth_app ||= OAuthApplication.create!(client_id: req.client_id, client_secret: req.client_secret) - if user.valid_password? req.password - res.access_token = o_auth_app.tokens.create!.bearer_token - end - else - req.unsupported_grant_type! - end - end - end -end diff --git a/spec/integration/openid/token_endpoint_spec.rb b/spec/integration/openid/token_endpoint_spec.rb new file mode 100644 index 000000000..2d8598177 --- /dev/null +++ b/spec/integration/openid/token_endpoint_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe "Token Endpoint", type: :request do + describe "password grant type" do + context "when the username field is missing" do + it "should return an invalid request error" do + post "/openid/access_tokens?grant_type=password\&password=bluepin7\&client_id=4\&client_secret=azerty" + expect(response.body).to include("'username' required") + end + end + context "when the password field is missing" do + it "should return an invalid request error" do + post "/openid/access_tokens?grant_type=password\&username=bob\&client_id=4\&client_secret=azerty" + expect(response.body).to include("'password' required") + end + end + context "when the username does not match an existing user" do + it "should return an invalid request error" do + post "/openid/access_tokens?grant_type=password\&username=mewasdfrandom\&password=bluepin7\&client_id=4\&client_secret=azerty" + expect(response.body).to include("invalid_grant") + end + end + context "when the password is invalid" do + it "should return an invalid request error" do + post "/openid/access_tokens?grant_type=password\&username=mewasdfrandom\&password=bluepin7\&client_id=4\&client_secret=azerty" + expect(response.body).to include("invalid_grant") + end + end + context "when there are duplicate fields" do + it "should return an invalid request error" do + post "/openid/access_tokens?grant_type=password\&username=bob\&password=bluepin6\&username=bob\&password=bluepin7\&client_id=4\&client_secret=azerty" + expect(response.body).to include("invalid_grant") + # TODO: Apparently Nov's implementation lets this one pass; however, according to the OIDC spec, we are supposed to reject duplicate fields. Is this a security issue? + end + end + context "when the client is unauthorized" do + # TODO: If we support password grant, we should prevent access from unauthorized client applications + it "should return an error" do + fail + end + end + context "when many unauthorized requests are made" do + # TODO: If we support password grant, we should support a way to prevent brute force attacks (using rate-limitation or generating alerts) as specified by RFC 6749 4.3.2 Access Token Request + it "should generate an alert" do + fail + end + end + context "when the request is valid" do + it "should return an access token" do + post "/openid/access_tokens?grant_type=password\&username=bob\&password=bluepin7\&client_id=4\&client_secret=azerty" + json = JSON.parse(response.body) + expect(json["access_token"].length).to eq(64) + expect(json["token_type"]).to eq("bearer") + expect(json.keys).to include("expires_in") + end + end + end + describe "unsupported grant type" do + it "should return an unsupported grant type error" do + post "/openid/access_tokens?grant_type=me\&username=bob\&password=bluepin7\&client_id=4\&client_secret=azerty" + expect(response.body).to include "unsupported_grant_type" + end + end +end diff --git a/spec/lib/openid_connect/token_endpoint_spec.rb b/spec/lib/openid_connect/token_endpoint_spec.rb deleted file mode 100644 index 44e6a26b1..000000000 --- a/spec/lib/openid_connect/token_endpoint_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'rspec' - -describe TokenEndpoint do - - it "shoud generate a token" do - - end -end From 68d96a3189a369249c2eacc9418892acfcd81ef2 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 13 Sep 2015 14:11:53 -0700 Subject: [PATCH 004/105] Add versionist gem --- Gemfile | 2 ++ Gemfile.lock | 6 ++++++ app/controllers/api/LICENSE | 19 +++++++++++++++++++ app/controllers/api/v2/base_controller.rb | 6 ++++++ app/controllers/api/v2/users_controller.rb | 5 +++++ app/controllers/users_controller.rb | 7 ------- app/presenters/api/v2/base_presenter.rb | 2 ++ app/serializers/user_serializer.rb | 3 +++ config/routes.rb | 7 +++++-- public/docs/v2/index.html | 17 +++++++++++++++++ public/docs/v2/style.css | 5 +++++ .../api/v2/base_controller_spec.rb | 5 +++++ spec/presenters/api/v2/base_presenter_spec.rb | 5 +++++ spec/requests/api/v2/base_controller_spec.rb | 5 +++++ 14 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 app/controllers/api/LICENSE create mode 100644 app/controllers/api/v2/base_controller.rb create mode 100644 app/controllers/api/v2/users_controller.rb create mode 100644 app/presenters/api/v2/base_presenter.rb create mode 100644 app/serializers/user_serializer.rb create mode 100644 public/docs/v2/index.html create mode 100644 public/docs/v2/style.css create mode 100644 spec/controllers/api/v2/base_controller_spec.rb create mode 100644 spec/presenters/api/v2/base_presenter_spec.rb create mode 100644 spec/requests/api/v2/base_controller_spec.rb diff --git a/Gemfile b/Gemfile index 72d743af0..4eb4c33bd 100644 --- a/Gemfile +++ b/Gemfile @@ -195,6 +195,8 @@ gem "rubyzip", "1.1.7" # https://github.com/discourse/discourse/pull/238 gem "minitest" +gem "versionist", "1.4.1" + # Windows and OSX have an execjs compatible runtime built-in, Linux users should # install Node.js or use "therubyracer". # diff --git a/Gemfile.lock b/Gemfile.lock index ad32b01d8..bef65ab00 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -849,6 +849,10 @@ GEM validate_url (1.0.2) activemodel (>= 3.0.0) addressable + versionist (1.4.1) + activesupport (>= 3) + railties (>= 3) + yard (~> 0.7) warden (1.2.4) rack (>= 1.0) webfinger (1.0.1) @@ -864,6 +868,7 @@ GEM xml-simple (1.1.5) xpath (2.0.0) nokogiri (~> 1.3) + yard (0.8.7.6) PLATFORMS ruby @@ -1008,6 +1013,7 @@ DEPENDENCIES uglifier (= 2.7.2) unicorn (= 5.0.1) uuid (= 2.3.8) + versionist (= 1.4.1) webmock (= 1.22.3) will_paginate (= 3.0.7) diff --git a/app/controllers/api/LICENSE b/app/controllers/api/LICENSE new file mode 100644 index 000000000..de7a5345d --- /dev/null +++ b/app/controllers/api/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Brian Ploetz (bploetz@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/app/controllers/api/v2/base_controller.rb b/app/controllers/api/v2/base_controller.rb new file mode 100644 index 000000000..33bfb9d71 --- /dev/null +++ b/app/controllers/api/v2/base_controller.rb @@ -0,0 +1,6 @@ +class Api::V2::BaseController < ApplicationController + include Openid::Authentication + + before_action :authenticate_user! + before_filter :require_access_token +end diff --git a/app/controllers/api/v2/users_controller.rb b/app/controllers/api/v2/users_controller.rb new file mode 100644 index 000000000..0a6d34114 --- /dev/null +++ b/app/controllers/api/v2/users_controller.rb @@ -0,0 +1,5 @@ +class Api::V2::UsersController < Api::V2::BaseController + def show + render json: current_user + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 823f2ba3e..91376d45f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,17 +3,10 @@ # the COPYRIGHT file. class UsersController < ApplicationController - include Openid::Authentication before_action :authenticate_user!, except: [:new, :create, :public, :user_photo] - before_filter :require_access_token, only: [:show] respond_to :html - # TODO: Adjust so that it sends back only required elements, e.g, should not send hashed password (serialized_private_key) back - def show - render json: current_user - end - def edit @aspect = :user_edit @user = current_user diff --git a/app/presenters/api/v2/base_presenter.rb b/app/presenters/api/v2/base_presenter.rb new file mode 100644 index 000000000..2afabc56a --- /dev/null +++ b/app/presenters/api/v2/base_presenter.rb @@ -0,0 +1,2 @@ +class Api::V2::BasePresenter +end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb new file mode 100644 index 000000000..b0d134815 --- /dev/null +++ b/app/serializers/user_serializer.rb @@ -0,0 +1,3 @@ +class UserSerializer < ActiveModel::Serializer + attributes :name, :email, :language, :username +end diff --git a/config/routes.rb b/config/routes.rb index 8dfbfd08a..3cb9501cb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -245,8 +245,11 @@ Diaspora::Application.routes.draw do resource :openid do resources :authorizations, only: [:new, :create] match 'connect', to: 'connect#show', via: [:get, :post] - match '.well-known/:id', to: 'discovery#show' , :via => [:get, :post] + match '.well-known/:id', to: 'discovery#show' , via: [:get, :post] post 'access_tokens', to: proc { |env| Openid::TokenEndpoint.new.call(env) } - match 'user_info', to: 'users#show', :via => [:get, :post] + end + + api_version(:module => "Api::V2", path: {value: "api/v2"}, default: true) do + match 'user', to: 'users#show', via: :get end end diff --git a/public/docs/v2/index.html b/public/docs/v2/index.html new file mode 100644 index 000000000..518259ab3 --- /dev/null +++ b/public/docs/v2/index.html @@ -0,0 +1,17 @@ + + + + Documentation for v2 + + + +
+
+

API Operations

+
+
+

Documentation for v2

+
+
+ + diff --git a/public/docs/v2/style.css b/public/docs/v2/style.css new file mode 100644 index 000000000..ea6e1ce9c --- /dev/null +++ b/public/docs/v2/style.css @@ -0,0 +1,5 @@ +body {margin: 0; background-color: #fff; color: #000; font-family: Arial,sans-serif;} +content {margin-left: 200px;} +content h1 {text-align: center;} +operations {float: left; width: 200px; border-right: 1px solid #ccc;} +operations h3 {text-align: center;} diff --git a/spec/controllers/api/v2/base_controller_spec.rb b/spec/controllers/api/v2/base_controller_spec.rb new file mode 100644 index 000000000..221629acb --- /dev/null +++ b/spec/controllers/api/v2/base_controller_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Api::V2::BaseController do + +end diff --git a/spec/presenters/api/v2/base_presenter_spec.rb b/spec/presenters/api/v2/base_presenter_spec.rb new file mode 100644 index 000000000..f9ab6b5df --- /dev/null +++ b/spec/presenters/api/v2/base_presenter_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Api::V2::BasePresenter do + +end diff --git a/spec/requests/api/v2/base_controller_spec.rb b/spec/requests/api/v2/base_controller_spec.rb new file mode 100644 index 000000000..221629acb --- /dev/null +++ b/spec/requests/api/v2/base_controller_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Api::V2::BaseController do + +end From beae77102df176caca4b0a798e979ebd2b06370b Mon Sep 17 00:00:00 2001 From: theworldbright Date: Mon, 6 Jul 2015 20:31:52 +0900 Subject: [PATCH 005/105] Allow current user to be obtained from access token --- app/controllers/api/v2/base_controller.rb | 3 +- app/controllers/api/v2/users_controller.rb | 8 +++- app/models/o_auth_application.rb | 3 ++ config/routes.rb | 2 +- ...150613202109_create_o_auth_applications.rb | 7 +++- db/migrate/20150614134031_create_tokens.rb | 2 +- db/schema.rb | 5 +++ features/desktop/oauth_password_flow.feature | 19 +++++++++ features/desktop/protected_resource.feature | 31 --------------- features/step_definitions/openid_steps.rb | 4 +- lib/openid/token_endpoint.rb | 35 ----------------- lib/{openid => openid_connect}/LICENSE | 0 .../authentication.rb | 6 +-- .../authorization_endpoint.rb | 2 +- lib/openid_connect/token_endpoint.rb | 39 +++++++++++++++++++ .../api/v2/users_controller_spec.rb | 19 +++++++++ .../openid_connect}/token_endpoint_spec.rb | 2 +- spec/spec_helper.rb | 8 ++++ 18 files changed, 116 insertions(+), 79 deletions(-) create mode 100644 features/desktop/oauth_password_flow.feature delete mode 100644 features/desktop/protected_resource.feature delete mode 100644 lib/openid/token_endpoint.rb rename lib/{openid => openid_connect}/LICENSE (100%) rename lib/{openid => openid_connect}/authentication.rb (90%) rename lib/{openid => openid_connect}/authorization_endpoint.rb (95%) create mode 100644 lib/openid_connect/token_endpoint.rb create mode 100644 spec/controllers/api/v2/users_controller_spec.rb rename spec/{integration/openid => lib/openid_connect}/token_endpoint_spec.rb (98%) diff --git a/app/controllers/api/v2/base_controller.rb b/app/controllers/api/v2/base_controller.rb index 33bfb9d71..a9d85bcc4 100644 --- a/app/controllers/api/v2/base_controller.rb +++ b/app/controllers/api/v2/base_controller.rb @@ -1,6 +1,5 @@ class Api::V2::BaseController < ApplicationController - include Openid::Authentication + include OpenidConnect::Authentication - before_action :authenticate_user! before_filter :require_access_token end diff --git a/app/controllers/api/v2/users_controller.rb b/app/controllers/api/v2/users_controller.rb index 0a6d34114..7f8ea55b7 100644 --- a/app/controllers/api/v2/users_controller.rb +++ b/app/controllers/api/v2/users_controller.rb @@ -1,5 +1,11 @@ class Api::V2::UsersController < Api::V2::BaseController + def show - render json: current_user + render json: user + end + +private + def user + current_token.o_auth_application.user end end diff --git a/app/models/o_auth_application.rb b/app/models/o_auth_application.rb index 29d3cd87e..11ed470c1 100644 --- a/app/models/o_auth_application.rb +++ b/app/models/o_auth_application.rb @@ -1,4 +1,7 @@ class OAuthApplication < ActiveRecord::Base + belongs_to :user + + validates :user_id, presence: true validates :client_id, presence: true, uniqueness: true validates :client_secret, presence: true diff --git a/config/routes.rb b/config/routes.rb index 3cb9501cb..02d62211f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -246,7 +246,7 @@ Diaspora::Application.routes.draw do resources :authorizations, only: [:new, :create] match 'connect', to: 'connect#show', via: [:get, :post] match '.well-known/:id', to: 'discovery#show' , via: [:get, :post] - post 'access_tokens', to: proc { |env| Openid::TokenEndpoint.new.call(env) } + post 'access_tokens', to: proc { |env| OpenidConnect::TokenEndpoint.new.call(env) } end api_version(:module => "Api::V2", path: {value: "api/v2"}, default: true) do diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index 1ad4638d2..b9309064d 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -1,10 +1,15 @@ class CreateOAuthApplications < ActiveRecord::Migration - def change + def self.up create_table :o_auth_applications do |t| + t.belongs_to :user, index: true t.string :client_id t.string :client_secret t.timestamps null: false end end + + def self.down + drop_table :o_auth_applications + end end diff --git a/db/migrate/20150614134031_create_tokens.rb b/db/migrate/20150614134031_create_tokens.rb index 613a7f53d..9db140791 100644 --- a/db/migrate/20150614134031_create_tokens.rb +++ b/db/migrate/20150614134031_create_tokens.rb @@ -1,7 +1,7 @@ class CreateTokens < ActiveRecord::Migration def self.up create_table :tokens do |t| - t.belongs_to :o_auth_application + t.belongs_to :o_auth_application, index: true t.string :token t.datetime :expires_at t.timestamps null: false diff --git a/db/schema.rb b/db/schema.rb index 77fd2e4c4..7b36511f8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -237,12 +237,15 @@ ActiveRecord::Schema.define(version: 20151003142048) do add_index "notifications", ["target_type", "target_id"], name: "index_notifications_on_target_type_and_target_id", length: {"target_type"=>190, "target_id"=>nil}, using: :btree create_table "o_auth_applications", force: :cascade do |t| + t.integer "user_id", limit: 4 t.string "client_id", limit: 255 t.string "client_secret", limit: 255 t.datetime "created_at", null: false t.datetime "updated_at", null: false end + add_index "o_auth_applications", ["user_id"], name: "index_o_auth_applications_on_user_id", using: :btree + create_table "o_embed_caches", force: :cascade do |t| t.string "url", limit: 1024, null: false t.text "data", limit: 65535, null: false @@ -543,6 +546,8 @@ ActiveRecord::Schema.define(version: 20151003142048) do t.datetime "updated_at", null: false end + add_index "tokens", ["o_auth_application_id"], name: "index_tokens_on_o_auth_application_id", using: :btree + create_table "user_preferences", force: :cascade do |t| t.string "email_type", limit: 255 t.integer "user_id", limit: 4 diff --git a/features/desktop/oauth_password_flow.feature b/features/desktop/oauth_password_flow.feature new file mode 100644 index 000000000..4e28d825f --- /dev/null +++ b/features/desktop/oauth_password_flow.feature @@ -0,0 +1,19 @@ +@javascript +Feature: Access protected resources using password flow + # TODO: Add tests for expired access tokens + Background: + Given a user with username "kent" + + Scenario: Valid bearer tokens sent via Authorization Request Header Field + + Scenario: Valid bearer tokens sent via Form Encoded Parameter + + Scenario: Valid bearer tokens sent via URI query parameter + When I send a post request to the token endpoint using "kent"'s credentials + And I use received valid bearer tokens to access user info via URI query parameter + Then I should receive "kent"'s id, username, and email + + Scenario: Invalid bearer tokens sent via URI query parameter + When I send a post request to the token endpoint using "kent"'s credentials + And I use invalid bearer tokens to access user info via URI query parameter + Then I should receive an "invalid_token" error diff --git a/features/desktop/protected_resource.feature b/features/desktop/protected_resource.feature deleted file mode 100644 index 4d0cee1ec..000000000 --- a/features/desktop/protected_resource.feature +++ /dev/null @@ -1,31 +0,0 @@ -@javascript -# TODO: Add tests for expired access tokens -# TODO: Add tests to check for WWW-Authenticate response header field as according to RFC 6750 -Feature: Access protected resources using bearer access token - Background: - Given a user with username "bob" - And I log in manually as "bob" with password "password" - And I send a post request to the token endpoint using "bob"'s credentials - - Scenario: Valid bearer tokens sent via Authorization Request Header Field - # TODO: Add tests - - Scenario: Valid bearer tokens sent via Form Encoded Parameter - # TODO: Add tests - - Scenario: Valid bearer tokens sent via URI query parameter - When I use received valid bearer tokens to access user info via URI query parameter - Then I should receive "bob"'s id, username, and email - # TODO: I want to confirm that the cache-control header in the response is private as according to RFC 6750 - # Unfortunately, selenium doesn't allow access to response headers - - Scenario: Invalid bearer tokens sent via URI query parameter - When I use invalid bearer tokens to access user info via URI query parameter - Then I should receive an "invalid_token" error - - Scenario: Valid bearer tokens sent via URI query parameter but user is logged out - When I log out manually - And I use received valid bearer tokens to access user info via URI query parameter - Then I should see "Sign in" in the content - When I log in manually as "bob" with password "password" - Then I should receive "bob"'s id, username, and email diff --git a/features/step_definitions/openid_steps.rb b/features/step_definitions/openid_steps.rb index 180711c35..ef1974da8 100644 --- a/features/step_definitions/openid_steps.rb +++ b/features/step_definitions/openid_steps.rb @@ -10,13 +10,13 @@ end When /^I use received valid bearer tokens to access user info via URI query parameter$/ do accessTokenJson = JSON.parse(last_response.body) - userInfoEndPointURL = "/openid/user_info/" + userInfoEndPointURL = "/api/v2/user/" userInfoEndPointURLQuery = "?access_token=" + accessTokenJson["access_token"] visit userInfoEndPointURL + userInfoEndPointURLQuery end When /^I use invalid bearer tokens to access user info via URI query parameter$/ do - userInfoEndPointURL = "/openid/user_info/" + userInfoEndPointURL = "/api/v2/user/" userInfoEndPointURLQuery = "?access_token=" + SecureRandom.hex(32) visit userInfoEndPointURL + userInfoEndPointURLQuery end diff --git a/lib/openid/token_endpoint.rb b/lib/openid/token_endpoint.rb deleted file mode 100644 index ae52a0305..000000000 --- a/lib/openid/token_endpoint.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Openid - class TokenEndpoint - attr_accessor :app - delegate :call, to: :app - - def initialize - @app = Rack::OAuth2::Server::Token.new do |req, res| - case req.grant_type - when :password - o_auth_app = retrieveOrCreateNewClientApplication(req) - user = User.find_for_database_authentication(username: req.username) - if o_auth_app && user && user.valid_password?(req.password) - res.access_token = o_auth_app.tokens.create!.bearer_token - else - req.invalid_grant! - end - else - res.unsupported_grant_type! - end - end - end - - def retrieveOrCreateNewClientApplication(req) - retrieveClient(req) || createClient(req) - end - - def retrieveClient(req) - OAuthApplication.find_by_client_id req.client_id - end - - def createClient(req) - OAuthApplication.create!(client_id: req.client_id, client_secret: req.client_secret) - end - end -end diff --git a/lib/openid/LICENSE b/lib/openid_connect/LICENSE similarity index 100% rename from lib/openid/LICENSE rename to lib/openid_connect/LICENSE diff --git a/lib/openid/authentication.rb b/lib/openid_connect/authentication.rb similarity index 90% rename from lib/openid/authentication.rb rename to lib/openid_connect/authentication.rb index 7423ba21e..eca5573a0 100644 --- a/lib/openid/authentication.rb +++ b/lib/openid_connect/authentication.rb @@ -1,4 +1,4 @@ -module Openid +module OpenidConnect module Authentication def self.included(klass) @@ -22,9 +22,9 @@ module Openid end end - # Scopes should be implemented here + # TODO: Scopes should be implemented here def required_scopes - nil # as default + nil end end end diff --git a/lib/openid/authorization_endpoint.rb b/lib/openid_connect/authorization_endpoint.rb similarity index 95% rename from lib/openid/authorization_endpoint.rb rename to lib/openid_connect/authorization_endpoint.rb index 3b8ea9a55..61fbe11ce 100644 --- a/lib/openid/authorization_endpoint.rb +++ b/lib/openid_connect/authorization_endpoint.rb @@ -1,4 +1,4 @@ -module Openid +module OpenidConnect class AuthorizationEndpoint attr_accessor :app, :account, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object delegate :call, to: :app diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb new file mode 100644 index 000000000..070ae4549 --- /dev/null +++ b/lib/openid_connect/token_endpoint.rb @@ -0,0 +1,39 @@ +module OpenidConnect + class TokenEndpoint + attr_accessor :app + delegate :call, to: :app + + def initialize + @app = Rack::OAuth2::Server::Token.new do |req, res| + case req.grant_type + when :password + user = User.find_for_database_authentication(username: req.username) + if user + o_auth_app = retrieveOrCreateNewClientApplication(req, user) + if o_auth_app && user.valid_password?(req.password) + res.access_token = o_auth_app.tokens.create!.bearer_token + else + req.invalid_grant! + end + else + req.invalid_grant! # TODO: Change to user login + end + else + res.unsupported_grant_type! + end + end + end + + def retrieveOrCreateNewClientApplication(req, user) + retrieveClient(req, user) || createClient(req, user) + end + + def retrieveClient(req, user) + user.o_auth_applications.find_by_client_id req.client_id + end + + def createClient(req, user) + user.o_auth_applications.create!(client_id: req.client_id, client_secret: req.client_secret) + end + end +end diff --git a/spec/controllers/api/v2/users_controller_spec.rb b/spec/controllers/api/v2/users_controller_spec.rb new file mode 100644 index 000000000..fd2230962 --- /dev/null +++ b/spec/controllers/api/v2/users_controller_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +# TODO: Confirm that the cache-control header in the response is private as according to RFC 6750 +# TODO: Check for WWW-Authenticate response header field as according to RFC 6750 +describe Api::V2::UsersController, type: :request do + describe "#show" do + let!(:application) { bob.o_auth_applications.create!(client_id: 1, client_secret: "secret") } + let!(:token) { application.tokens.create!.bearer_token.to_s } + + context "when valid" do + it "shows the user's info" do + get "/api/v2/user/?access_token=" + token + jsonBody = JSON.parse(response.body) + expect(jsonBody["username"]).to eq(bob.username) + expect(jsonBody["email"]).to eq(bob.email) + end + end + end +end diff --git a/spec/integration/openid/token_endpoint_spec.rb b/spec/lib/openid_connect/token_endpoint_spec.rb similarity index 98% rename from spec/integration/openid/token_endpoint_spec.rb rename to spec/lib/openid_connect/token_endpoint_spec.rb index 2d8598177..4325a05e0 100644 --- a/spec/integration/openid/token_endpoint_spec.rb +++ b/spec/lib/openid_connect/token_endpoint_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe "Token Endpoint", type: :request do +describe OpenidConnect::TokenEndpoint, type: :request do describe "password grant type" do context "when the username field is missing" do it "should return an invalid request error" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ca444256a..dcdb16d05 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -59,6 +59,14 @@ def photo_fixture_name @photo_fixture_name = File.join(File.dirname(__FILE__), "fixtures", "button.png") end +def retrieveAccessToken(user) + o_auth_app = OAuthApplication.create!(client_id: 4, client_secret: "azerty") + user = User.find_for_database_authentication(username: user.username) + if o_auth_app && user && user.valid_password?("bluepin7") # Hard coded password for bob + o_auth_app.tokens.create!.bearer_token.to_s + end +end + # Force fixture rebuild FileUtils.rm_f(Rails.root.join("tmp", "fixture_builder.yml")) From 9de2837a638a20d199ac475e9ef24165d5764f28 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Tue, 7 Jul 2015 15:22:35 +0900 Subject: [PATCH 006/105] Move new API from /api/v2 to /api/v0 --- app/controllers/api/{v2 => v0}/base_controller.rb | 2 +- app/controllers/api/{v2 => v0}/users_controller.rb | 2 +- app/presenters/api/v0/base_presenter.rb | 2 ++ app/presenters/api/v2/base_presenter.rb | 2 -- config/routes.rb | 10 +--------- features/step_definitions/openid_steps.rb | 4 ++-- public/docs/{v2 => v0}/index.html | 4 ++-- public/docs/{v2 => v0}/style.css | 0 spec/controllers/api/v0/base_controller_spec.rb | 5 +++++ .../api/{v2 => v0}/users_controller_spec.rb | 4 ++-- spec/controllers/api/v2/base_controller_spec.rb | 5 ----- spec/presenters/api/v0/base_presenter_spec.rb | 5 +++++ spec/presenters/api/v2/base_presenter_spec.rb | 5 ----- spec/requests/api/v2/base_controller_spec.rb | 2 +- 14 files changed, 22 insertions(+), 30 deletions(-) rename app/controllers/api/{v2 => v0}/base_controller.rb (60%) rename app/controllers/api/{v2 => v0}/users_controller.rb (66%) create mode 100644 app/presenters/api/v0/base_presenter.rb delete mode 100644 app/presenters/api/v2/base_presenter.rb rename public/docs/{v2 => v0}/index.html (79%) rename public/docs/{v2 => v0}/style.css (100%) create mode 100644 spec/controllers/api/v0/base_controller_spec.rb rename spec/controllers/api/{v2 => v0}/users_controller_spec.rb (86%) delete mode 100644 spec/controllers/api/v2/base_controller_spec.rb create mode 100644 spec/presenters/api/v0/base_presenter_spec.rb delete mode 100644 spec/presenters/api/v2/base_presenter_spec.rb diff --git a/app/controllers/api/v2/base_controller.rb b/app/controllers/api/v0/base_controller.rb similarity index 60% rename from app/controllers/api/v2/base_controller.rb rename to app/controllers/api/v0/base_controller.rb index a9d85bcc4..0899a13a8 100644 --- a/app/controllers/api/v2/base_controller.rb +++ b/app/controllers/api/v0/base_controller.rb @@ -1,4 +1,4 @@ -class Api::V2::BaseController < ApplicationController +class Api::V0::BaseController < ApplicationController include OpenidConnect::Authentication before_filter :require_access_token diff --git a/app/controllers/api/v2/users_controller.rb b/app/controllers/api/v0/users_controller.rb similarity index 66% rename from app/controllers/api/v2/users_controller.rb rename to app/controllers/api/v0/users_controller.rb index 7f8ea55b7..e57c7cd23 100644 --- a/app/controllers/api/v2/users_controller.rb +++ b/app/controllers/api/v0/users_controller.rb @@ -1,4 +1,4 @@ -class Api::V2::UsersController < Api::V2::BaseController +class Api::V0::UsersController < Api::V0::BaseController def show render json: user diff --git a/app/presenters/api/v0/base_presenter.rb b/app/presenters/api/v0/base_presenter.rb new file mode 100644 index 000000000..b425b1440 --- /dev/null +++ b/app/presenters/api/v0/base_presenter.rb @@ -0,0 +1,2 @@ +class Api::V0::BasePresenter +end diff --git a/app/presenters/api/v2/base_presenter.rb b/app/presenters/api/v2/base_presenter.rb deleted file mode 100644 index 2afabc56a..000000000 --- a/app/presenters/api/v2/base_presenter.rb +++ /dev/null @@ -1,2 +0,0 @@ -class Api::V2::BasePresenter -end diff --git a/config/routes.rb b/config/routes.rb index 02d62211f..58c7d7445 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -198,15 +198,7 @@ Diaspora::Application.routes.draw do end end - scope 'api/v0', :controller => :apis do - get :me - end - namespace :api do - namespace :v0 do - get "/users/:username" => 'users#show', :as => 'user' - get "/tags/:name" => 'tags#show', :as => 'tag' - end namespace :v1 do resources :tokens, :only => [:create, :destroy] end @@ -249,7 +241,7 @@ Diaspora::Application.routes.draw do post 'access_tokens', to: proc { |env| OpenidConnect::TokenEndpoint.new.call(env) } end - api_version(:module => "Api::V2", path: {value: "api/v2"}, default: true) do + api_version(module: "Api::V0", path: {value: "api/v0"}, default: true) do match 'user', to: 'users#show', via: :get end end diff --git a/features/step_definitions/openid_steps.rb b/features/step_definitions/openid_steps.rb index ef1974da8..47fada788 100644 --- a/features/step_definitions/openid_steps.rb +++ b/features/step_definitions/openid_steps.rb @@ -10,13 +10,13 @@ end When /^I use received valid bearer tokens to access user info via URI query parameter$/ do accessTokenJson = JSON.parse(last_response.body) - userInfoEndPointURL = "/api/v2/user/" + userInfoEndPointURL = "/api/v0/user/" userInfoEndPointURLQuery = "?access_token=" + accessTokenJson["access_token"] visit userInfoEndPointURL + userInfoEndPointURLQuery end When /^I use invalid bearer tokens to access user info via URI query parameter$/ do - userInfoEndPointURL = "/api/v2/user/" + userInfoEndPointURL = "/api/v0/user/" userInfoEndPointURLQuery = "?access_token=" + SecureRandom.hex(32) visit userInfoEndPointURL + userInfoEndPointURLQuery end diff --git a/public/docs/v2/index.html b/public/docs/v0/index.html similarity index 79% rename from public/docs/v2/index.html rename to public/docs/v0/index.html index 518259ab3..08b6b913f 100644 --- a/public/docs/v2/index.html +++ b/public/docs/v0/index.html @@ -1,7 +1,7 @@ - Documentation for v2 + Documentation for v0 @@ -10,7 +10,7 @@

API Operations

-

Documentation for v2

+

Documentation for v0

diff --git a/public/docs/v2/style.css b/public/docs/v0/style.css similarity index 100% rename from public/docs/v2/style.css rename to public/docs/v0/style.css diff --git a/spec/controllers/api/v0/base_controller_spec.rb b/spec/controllers/api/v0/base_controller_spec.rb new file mode 100644 index 000000000..f637d15b8 --- /dev/null +++ b/spec/controllers/api/v0/base_controller_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Api::V0::BaseController do + +end diff --git a/spec/controllers/api/v2/users_controller_spec.rb b/spec/controllers/api/v0/users_controller_spec.rb similarity index 86% rename from spec/controllers/api/v2/users_controller_spec.rb rename to spec/controllers/api/v0/users_controller_spec.rb index fd2230962..83d7128d2 100644 --- a/spec/controllers/api/v2/users_controller_spec.rb +++ b/spec/controllers/api/v0/users_controller_spec.rb @@ -2,14 +2,14 @@ require 'spec_helper' # TODO: Confirm that the cache-control header in the response is private as according to RFC 6750 # TODO: Check for WWW-Authenticate response header field as according to RFC 6750 -describe Api::V2::UsersController, type: :request do +describe Api::V0::UsersController, type: :request do describe "#show" do let!(:application) { bob.o_auth_applications.create!(client_id: 1, client_secret: "secret") } let!(:token) { application.tokens.create!.bearer_token.to_s } context "when valid" do it "shows the user's info" do - get "/api/v2/user/?access_token=" + token + get "/api/v0/user/?access_token=" + token jsonBody = JSON.parse(response.body) expect(jsonBody["username"]).to eq(bob.username) expect(jsonBody["email"]).to eq(bob.email) diff --git a/spec/controllers/api/v2/base_controller_spec.rb b/spec/controllers/api/v2/base_controller_spec.rb deleted file mode 100644 index 221629acb..000000000 --- a/spec/controllers/api/v2/base_controller_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'spec_helper' - -describe Api::V2::BaseController do - -end diff --git a/spec/presenters/api/v0/base_presenter_spec.rb b/spec/presenters/api/v0/base_presenter_spec.rb new file mode 100644 index 000000000..36d38fbe7 --- /dev/null +++ b/spec/presenters/api/v0/base_presenter_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Api::V0::BasePresenter do + +end diff --git a/spec/presenters/api/v2/base_presenter_spec.rb b/spec/presenters/api/v2/base_presenter_spec.rb deleted file mode 100644 index f9ab6b5df..000000000 --- a/spec/presenters/api/v2/base_presenter_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'spec_helper' - -describe Api::V2::BasePresenter do - -end diff --git a/spec/requests/api/v2/base_controller_spec.rb b/spec/requests/api/v2/base_controller_spec.rb index 221629acb..f637d15b8 100644 --- a/spec/requests/api/v2/base_controller_spec.rb +++ b/spec/requests/api/v2/base_controller_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -describe Api::V2::BaseController do +describe Api::V0::BaseController do end From 52e10a91fedf9275125f2cdf0ab9de05ad20c418 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Tue, 7 Jul 2015 17:06:24 +0900 Subject: [PATCH 007/105] Add tests for invalid token to password flow --- .../api/v0/users_controller_spec.rb | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/spec/controllers/api/v0/users_controller_spec.rb b/spec/controllers/api/v0/users_controller_spec.rb index 83d7128d2..caa874f54 100644 --- a/spec/controllers/api/v0/users_controller_spec.rb +++ b/spec/controllers/api/v0/users_controller_spec.rb @@ -1,19 +1,48 @@ require 'spec_helper' -# TODO: Confirm that the cache-control header in the response is private as according to RFC 6750 -# TODO: Check for WWW-Authenticate response header field as according to RFC 6750 describe Api::V0::UsersController, type: :request do describe "#show" do let!(:application) { bob.o_auth_applications.create!(client_id: 1, client_secret: "secret") } let!(:token) { application.tokens.create!.bearer_token.to_s } + let(:invalid_token) { SecureRandom.hex(32).to_s } context "when valid" do - it "shows the user's info" do + it "shows the user's username and email" do get "/api/v0/user/?access_token=" + token jsonBody = JSON.parse(response.body) expect(jsonBody["username"]).to eq(bob.username) expect(jsonBody["email"]).to eq(bob.email) end + it "should include private in the cache-control header" do + get "/api/v0/user/?access_token=" + token + expect(response.headers["Cache-Control"]).to include("private") + end + end + + context "when no access token is provided" do + it "should respond with a 401 Unauthorized response" do + get "/api/v0/user/" + expect(response.status).to be(401) + end + it "should have an auth-scheme value of Bearer" do + get "/api/v0/user/" + expect(response.headers["WWW-Authenticate"]).to include("Bearer") + end + end + + context "when an invalid access token is provided" do + it "should respond with a 401 Unauthorized response" do + get "/api/v0/user/?access_token=" + invalid_token + expect(response.status).to be(401) + end + it "should have an auth-scheme value of Bearer" do + get "/api/v0/user/?access_token=" + invalid_token + expect(response.headers["WWW-Authenticate"]).to include("Bearer") + end + it "should contain an invalid_token error" do + get "/api/v0/user/?access_token=" + invalid_token + expect(response.body).to include("invalid_token") + end end end end From 3fc0f64c56f33e2a8013813872400e3666dc720c Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 13 Sep 2015 14:13:38 -0700 Subject: [PATCH 008/105] Move openid controllers to openid connect namespace --- Gemfile | 2 +- Gemfile.lock | 14 +++++++------- app/controllers/openid/connect_controller.rb | 4 ---- app/controllers/{openid => openid_connect}/LICENSE | 0 .../authorizations_controller.rb | 12 ++++++------ .../discovery_controller.rb | 0 config/routes.rb | 7 +++---- 7 files changed, 17 insertions(+), 22 deletions(-) delete mode 100644 app/controllers/openid/connect_controller.rb rename app/controllers/{openid => openid_connect}/LICENSE (100%) rename app/controllers/{openid => openid_connect}/authorizations_controller.rb (80%) rename app/controllers/{openid => openid_connect}/discovery_controller.rb (100%) diff --git a/Gemfile b/Gemfile index 4eb4c33bd..b2bc08b19 100644 --- a/Gemfile +++ b/Gemfile @@ -150,7 +150,7 @@ gem "twitter", "5.15.0" gem "omniauth-wordpress", "0.2.2" # OpenID Connect -gem "openid_connect" +gem "openid_connect", "0.8.3" # Serializers diff --git a/Gemfile.lock b/Gemfile.lock index bef65ab00..4369e1425 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -522,17 +522,17 @@ GEM open_graph_reader (0.6.1) faraday (~> 0.9.0) nokogiri (~> 1.6) - openid_connect (0.9.2) + openid_connect (0.8.3) activemodel - attr_required (>= 1.0.0) + attr_required (>= 0.0.5) json (>= 1.4.3) - json-jwt (>= 1.5.0) - rack-oauth2 (>= 1.2.1) - swd (>= 1.0.0) + json-jwt (>= 0.5.5) + rack-oauth2 (>= 1.0.0) + swd (>= 0.1.2) tzinfo validate_email validate_url - webfinger (>= 1.0.1) + webfinger (>= 0.0.2) orm_adapter (0.5.0) parser (2.2.3.0) ast (>= 1.1, < 3.0) @@ -942,7 +942,7 @@ DEPENDENCIES omniauth-twitter (= 1.2.1) omniauth-wordpress (= 0.2.2) open_graph_reader (= 0.6.1) - openid_connect + openid_connect (= 0.8.3) pg (= 0.18.4) pronto (= 0.5.3) pronto-haml (= 0.5.0) diff --git a/app/controllers/openid/connect_controller.rb b/app/controllers/openid/connect_controller.rb deleted file mode 100644 index c552988a6..000000000 --- a/app/controllers/openid/connect_controller.rb +++ /dev/null @@ -1,4 +0,0 @@ -class ConnectController < ApplicationController - def show - end -end diff --git a/app/controllers/openid/LICENSE b/app/controllers/openid_connect/LICENSE similarity index 100% rename from app/controllers/openid/LICENSE rename to app/controllers/openid_connect/LICENSE diff --git a/app/controllers/openid/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb similarity index 80% rename from app/controllers/openid/authorizations_controller.rb rename to app/controllers/openid_connect/authorizations_controller.rb index f1d407043..fcbe04dfa 100644 --- a/app/controllers/openid/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -5,31 +5,31 @@ class AuthorizationsController < ApplicationController render :error, status: e.status end + before_action :authenticate_user! + def new call_authorization_endpoint end def create - call_authorization_endpoint :allow_approval, params[:approve] + call_authorization_endpoint :is_create, params[:approve] end private - def call_authorization_endpoint(allow_approval = false, approved = false) - endpoint = AuthorizationEndpoint.new allow_approval, approved + def call_authorization_endpoint(is_create = false, approved = false) + endpoint = AuthorizationEndpoint.new current_user, is_create, approved rack_response = *endpoint.call(request.env) @client, @response_type, @redirect_uri, @scopes, @_request_, @request_uri, @request_object = *[ endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint._request_, endpoint.request_uri, endpoint.request_object ] - require_authentication if ( - !allow_approval && + !is_create && (max_age = @request_object.try(:id_token).try(:max_age)) && current_account.last_logged_in_at < max_age.seconds.ago ) flash[:notice] = 'Exceeded Max Age, Login Again' unauthenticate! - require_authentication end respond_as_rack_app *rack_response end diff --git a/app/controllers/openid/discovery_controller.rb b/app/controllers/openid_connect/discovery_controller.rb similarity index 100% rename from app/controllers/openid/discovery_controller.rb rename to app/controllers/openid_connect/discovery_controller.rb diff --git a/config/routes.rb b/config/routes.rb index 58c7d7445..520068e76 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -234,14 +234,13 @@ Diaspora::Application.routes.draw do root :to => 'home#show' #OpenID Connect & OAuth - resource :openid do + namespace :openid_connect do + resources :clients, only: :create resources :authorizations, only: [:new, :create] - match 'connect', to: 'connect#show', via: [:get, :post] - match '.well-known/:id', to: 'discovery#show' , via: [:get, :post] post 'access_tokens', to: proc { |env| OpenidConnect::TokenEndpoint.new.call(env) } end api_version(module: "Api::V0", path: {value: "api/v0"}, default: true) do - match 'user', to: 'users#show', via: :get + match 'user', to: 'users#show', via: [:get, :post] end end From 7c75eb590117a670c45a86de1405116a60f3231a Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 8 Jul 2015 16:46:31 +0900 Subject: [PATCH 009/105] Make access tokens belong to user not client app --- app/models/o_auth_application.rb | 2 -- app/models/token.rb | 2 +- app/models/user.rb | 1 + .../20150613202109_create_o_auth_applications.rb | 2 ++ db/migrate/20150614134031_create_tokens.rb | 3 ++- db/schema.rb | 12 +++++++----- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/models/o_auth_application.rb b/app/models/o_auth_application.rb index 11ed470c1..0d85e25a9 100644 --- a/app/models/o_auth_application.rb +++ b/app/models/o_auth_application.rb @@ -1,9 +1,7 @@ class OAuthApplication < ActiveRecord::Base belongs_to :user - validates :user_id, presence: true validates :client_id, presence: true, uniqueness: true validates :client_secret, presence: true - has_many :tokens end diff --git a/app/models/token.rb b/app/models/token.rb index 37849715a..d466c71ca 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -1,5 +1,5 @@ class Token < ActiveRecord::Base - belongs_to :o_auth_application + belongs_to :user before_validation :setup, on: :create diff --git a/app/models/user.rb b/app/models/user.rb index 1a8ebf3f4..1ab98f71a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -77,6 +77,7 @@ class User < ActiveRecord::Base has_many :reports has_many :o_auth_applications + has_many :tokens before_save :guard_unconfirmed_email, :save_person! diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index b9309064d..8137f2b54 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -4,6 +4,8 @@ class CreateOAuthApplications < ActiveRecord::Migration t.belongs_to :user, index: true t.string :client_id t.string :client_secret + t.string :name + t.string :redirect_uris t.timestamps null: false end diff --git a/db/migrate/20150614134031_create_tokens.rb b/db/migrate/20150614134031_create_tokens.rb index 9db140791..5a5e292f4 100644 --- a/db/migrate/20150614134031_create_tokens.rb +++ b/db/migrate/20150614134031_create_tokens.rb @@ -1,9 +1,10 @@ class CreateTokens < ActiveRecord::Migration def self.up create_table :tokens do |t| - t.belongs_to :o_auth_application, index: true + t.belongs_to :user, index: true t.string :token t.datetime :expires_at + t.timestamps null: false end end diff --git a/db/schema.rb b/db/schema.rb index 7b36511f8..27ab1e24c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -240,6 +240,8 @@ ActiveRecord::Schema.define(version: 20151003142048) do t.integer "user_id", limit: 4 t.string "client_id", limit: 255 t.string "client_secret", limit: 255 + t.string "name", limit: 255 + t.string "redirect_uris", limit: 255 t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -539,14 +541,14 @@ ActiveRecord::Schema.define(version: 20151003142048) do add_index "tags", ["name"], name: "index_tags_on_name", unique: true, length: {"name"=>191}, using: :btree create_table "tokens", force: :cascade do |t| - t.integer "o_auth_application_id", limit: 4 - t.string "token", limit: 255 + t.integer "user_id", limit: 4 + t.string "token", limit: 255 t.datetime "expires_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "tokens", ["o_auth_application_id"], name: "index_tokens_on_o_auth_application_id", using: :btree + add_index "tokens", ["user_id"], name: "index_tokens_on_user_id", using: :btree create_table "user_preferences", force: :cascade do |t| t.string "email_type", limit: 255 From 88d02ea35bde229e87000947c18447fe28aff4d8 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 8 Jul 2015 16:48:36 +0900 Subject: [PATCH 010/105] Add client registration Client must now be registered prior to imitating a call to the token endpoint with the password flow. Squashed commits: [fdcef62] Rename authorization endpoint to protected resource endpoint --- app/controllers/api/v0/base_controller.rb | 2 +- app/controllers/api/v0/users_controller.rb | 2 +- .../authorizations_controller.rb | 2 +- .../openid_connect/clients_controller.rb | 30 +++++ app/models/o_auth_application.rb | 28 +++++ features/desktop/oauth_password_flow.feature | 27 ++--- features/step_definitions/openid_steps.rb | 71 ++++++++---- lib/openid_connect/authorization/endpoint.rb | 50 +++++++++ lib/openid_connect/authorization_endpoint.rb | 12 -- ...tion.rb => protected_resource_endpoint.rb} | 4 +- lib/openid_connect/token_endpoint.rb | 50 +++++---- .../openid_connect/clients_controller_spec.rb | 23 ++++ .../protected_resource_endpoint_spec.rb} | 35 ++++-- .../lib/openid_connect/token_endpoint_spec.rb | 105 +++++++++++++----- 14 files changed, 331 insertions(+), 110 deletions(-) create mode 100644 app/controllers/openid_connect/clients_controller.rb create mode 100644 lib/openid_connect/authorization/endpoint.rb delete mode 100644 lib/openid_connect/authorization_endpoint.rb rename lib/openid_connect/{authentication.rb => protected_resource_endpoint.rb} (88%) create mode 100644 spec/controllers/openid_connect/clients_controller_spec.rb rename spec/{controllers/api/v0/users_controller_spec.rb => lib/openid_connect/protected_resource_endpoint_spec.rb} (63%) diff --git a/app/controllers/api/v0/base_controller.rb b/app/controllers/api/v0/base_controller.rb index 0899a13a8..f95c9c5c7 100644 --- a/app/controllers/api/v0/base_controller.rb +++ b/app/controllers/api/v0/base_controller.rb @@ -1,5 +1,5 @@ class Api::V0::BaseController < ApplicationController - include OpenidConnect::Authentication + include OpenidConnect::ProtectedResourceEndpoint before_filter :require_access_token end diff --git a/app/controllers/api/v0/users_controller.rb b/app/controllers/api/v0/users_controller.rb index e57c7cd23..60bd14589 100644 --- a/app/controllers/api/v0/users_controller.rb +++ b/app/controllers/api/v0/users_controller.rb @@ -6,6 +6,6 @@ class Api::V0::UsersController < Api::V0::BaseController private def user - current_token.o_auth_application.user + current_token.user end end diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index fcbe04dfa..c10c137be 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -18,7 +18,7 @@ class AuthorizationsController < ApplicationController private def call_authorization_endpoint(is_create = false, approved = false) - endpoint = AuthorizationEndpoint.new current_user, is_create, approved + endpoint = AuthorizationEndpoint.new is_create, approved rack_response = *endpoint.call(request.env) @client, @response_type, @redirect_uri, @scopes, @_request_, @request_uri, @request_object = *[ endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint._request_, endpoint.request_uri, endpoint.request_object diff --git a/app/controllers/openid_connect/clients_controller.rb b/app/controllers/openid_connect/clients_controller.rb new file mode 100644 index 000000000..e067b9ab2 --- /dev/null +++ b/app/controllers/openid_connect/clients_controller.rb @@ -0,0 +1,30 @@ +class OpenidConnect::ClientsController < ApplicationController + + rescue_from OpenIDConnect::HttpError do |e| + rewriteHTTPErrorPageAsJSON(e) + end + rescue_from OpenIDConnect::ValidationFailed do |e| + rewriteValidationFailErrorPageAsJSON(e) + end + + def create + registrar = OpenIDConnect::Client::Registrar.new(request.url, params) + client = OAuthApplication.register! registrar + render json: client + end + +private + + def rewriteHTTPErrorPageAsJSON(e) + render json: { + error: :invalid_request, + error_description: e.message + }, status: 400 + end + def rewriteValidationFailErrorPageAsJSON(e) + render json: { + error: :invalid_client_metadata, + error_description: e.message + }, status: 400 + end +end diff --git a/app/models/o_auth_application.rb b/app/models/o_auth_application.rb index 0d85e25a9..f40816b32 100644 --- a/app/models/o_auth_application.rb +++ b/app/models/o_auth_application.rb @@ -4,4 +4,32 @@ class OAuthApplication < ActiveRecord::Base validates :client_id, presence: true, uniqueness: true validates :client_secret, presence: true + before_validation :setup, on: :create + def setup + self.client_id = SecureRandom.hex(16) + self.client_secret = SecureRandom.hex(32) + end + + class << self + def register!(registrarHash) + registrarHash.validate! + buildClientApplication(registrarHash) + end + + def buildClientApplication(registrarHash) + client = OAuthApplication.create! + client.attributes = filterNilValues(registrarHash) + client.save! + client + end + + def filterNilValues(registrarHash) + { + name: registrarHash.client_name, + redirect_uris: registrarHash.redirect_uris + }.delete_if do |key, value| + value.nil? + end + end + end end diff --git a/features/desktop/oauth_password_flow.feature b/features/desktop/oauth_password_flow.feature index 4e28d825f..2829ac296 100644 --- a/features/desktop/oauth_password_flow.feature +++ b/features/desktop/oauth_password_flow.feature @@ -1,19 +1,20 @@ -@javascript Feature: Access protected resources using password flow - # TODO: Add tests for expired access tokens Background: Given a user with username "kent" - Scenario: Valid bearer tokens sent via Authorization Request Header Field + Scenario: Invalid credentials to token endpoint + When I register a new client + And I send a post request from that client to the token endpoint using invalid credentials + Then I should receive an "invalid_grant" error - Scenario: Valid bearer tokens sent via Form Encoded Parameter - - Scenario: Valid bearer tokens sent via URI query parameter - When I send a post request to the token endpoint using "kent"'s credentials - And I use received valid bearer tokens to access user info via URI query parameter - Then I should receive "kent"'s id, username, and email - - Scenario: Invalid bearer tokens sent via URI query parameter - When I send a post request to the token endpoint using "kent"'s credentials - And I use invalid bearer tokens to access user info via URI query parameter + Scenario: Invalid bearer tokens sent + When I register a new client + And I send a post request from that client to the token endpoint using "kent"'s credentials + And I use invalid bearer tokens to access user info Then I should receive an "invalid_token" error + + Scenario: Valid password flow + When I register a new client + And I send a post request from that client to the token endpoint using "kent"'s credentials + And I use received valid bearer tokens to access user info + Then I should receive "kent"'s id, username, and email diff --git a/features/step_definitions/openid_steps.rb b/features/step_definitions/openid_steps.rb index 47fada788..a90cc35d3 100644 --- a/features/step_definitions/openid_steps.rb +++ b/features/step_definitions/openid_steps.rb @@ -1,37 +1,64 @@ -# Password has been hard coded as all test accounts seem to have a password of "password" -Given /^I send a post request to the token endpoint using "([^\"]*)"'s credentials$/ do |username| - user = User.find_by(username: username) - tokenEndpointURL = "/openid/access_tokens" - tokenEndpointURLQuery = "?grant_type=password&username=" + - user.username + - "&password=password&client_id=4&client_secret=azerty" - post tokenEndpointURL + tokenEndpointURLQuery +When /^I register a new client$/ do + clientRegistrationURL = "/openid_connect/clients" + post clientRegistrationURL, + { + redirect_uris: ["http://localhost:3000"] # Not actually used + } end -When /^I use received valid bearer tokens to access user info via URI query parameter$/ do +Given /^I send a post request from that client to the token endpoint using "([^\"]*)"'s credentials$/ do |username| + clientJSON = JSON.parse(last_response.body) + user = User.find_by(username: username) + tokenEndpointURL = "/openid_connect/access_tokens" + post tokenEndpointURL, + { + grant_type: "password", + username: user.username, + password: "password", # Password has been hard coded as all test accounts seem to have a password of "password" + client_id: clientJSON["o_auth_application"]["client_id"], + client_secret: clientJSON["o_auth_application"]["client_secret"] + } +end + +Given /^I send a post request from that client to the token endpoint using invalid credentials$/ do + clientJSON = JSON.parse(last_response.body) + tokenEndpointURL = "/openid_connect/access_tokens" + post tokenEndpointURL, + { + grant_type: "password", + username: User.find_by(username: "bob"), + password: "wrongpassword", + client_id: clientJSON["o_auth_application"]["client_id"], + client_secret: clientJSON["o_auth_application"]["client_secret"] + } +end + +When /^I use received valid bearer tokens to access user info$/ do accessTokenJson = JSON.parse(last_response.body) userInfoEndPointURL = "/api/v0/user/" - userInfoEndPointURLQuery = "?access_token=" + accessTokenJson["access_token"] - visit userInfoEndPointURL + userInfoEndPointURLQuery + get userInfoEndPointURL, + { + access_token: accessTokenJson["access_token"] + } end -When /^I use invalid bearer tokens to access user info via URI query parameter$/ do +When /^I use invalid bearer tokens to access user info$/ do userInfoEndPointURL = "/api/v0/user/" - userInfoEndPointURLQuery = "?access_token=" + SecureRandom.hex(32) - visit userInfoEndPointURL + userInfoEndPointURLQuery + get userInfoEndPointURL, + { + access_token: SecureRandom.hex(32) + } end Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username| + userInfoJson = JSON.parse(last_response.body) user = User.find_by_username(username) - expect(page).to have_content(user.username) - expect(page).to have_content(user.language) - expect(page).to have_content(user.email) + expect(userInfoJson["username"]).to have_content(user.username) + expect(userInfoJson["language"]).to have_content(user.language) + expect(userInfoJson["email"]).to have_content(user.email) end Then /^I should receive an "([^\"]*)" error$/ do |error_message| - expect(page).to have_content(error_message) -end - -Then /^I should see "([^\"]*)" in the content$/ do |content| - expect(page).to have_content(content) + userInfoJson = JSON.parse(last_response.body) + expect(userInfoJson["error"]).to have_content(error_message) end diff --git a/lib/openid_connect/authorization/endpoint.rb b/lib/openid_connect/authorization/endpoint.rb new file mode 100644 index 000000000..cf2b359f3 --- /dev/null +++ b/lib/openid_connect/authorization/endpoint.rb @@ -0,0 +1,50 @@ +module OpenidConnect + module Authorization + class Endpoint + attr_accessor :app, :user, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object + delegate :call, to: :app + + def initialize(current_user) + @user = current_user + @app = Rack::OAuth2::Server::Authorize.new do |req, res| + buildClient(req) + buildRedirectURI(req, res) + verifyNonce(req, res) + buildScopes(req) + buildRequestObject(req) + if OAuthApplication.available_response_types.include? Array(req.response_type).collect(&:to_s).join(' ') + handleResponseType(req, res) + else + req.unsupported_response_type! + end + end + end + def buildClient(req) + @client = OAuthApplication.find_by_client_id(req.client_id) || req.bad_request! + end + def buildRedirectURI(req, res) + res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@client.redirect_uris) + end + def verifyNonce(req, res) + if res.protocol_params_location == :fragment && req.nonce.blank? + req.invalid_request! 'nonce required' + end + end + def buildScopes(req) + @scopes = req.scope.inject([]) do |_scopes_, scope| + _scopes_ << Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}" + end + end + def buildRequestObject(req) + @request_object = if (@_request_ = req.request).present? + OpenIDConnect::RequestObject.decode req.request, nil # @client.secret + elsif (@request_uri = req.request_uri).present? + OpenIDConnect::RequestObject.fetch req.request_uri, nil # @client.secret + end + end + def handleResponseType(req, res) + # Implemented by subclass + end + end + end +end diff --git a/lib/openid_connect/authorization_endpoint.rb b/lib/openid_connect/authorization_endpoint.rb deleted file mode 100644 index 61fbe11ce..000000000 --- a/lib/openid_connect/authorization_endpoint.rb +++ /dev/null @@ -1,12 +0,0 @@ -module OpenidConnect - class AuthorizationEndpoint - attr_accessor :app, :account, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object - delegate :call, to: :app - - def initialize(allow_approval = false, approved = false) - @app = Rack::OAuth2::Server::Authorize.new do |req, res| - req.unsupported_response_type! # TODO: not supported yet - end - end - end -end diff --git a/lib/openid_connect/authentication.rb b/lib/openid_connect/protected_resource_endpoint.rb similarity index 88% rename from lib/openid_connect/authentication.rb rename to lib/openid_connect/protected_resource_endpoint.rb index eca5573a0..fa840c18b 100644 --- a/lib/openid_connect/authentication.rb +++ b/lib/openid_connect/protected_resource_endpoint.rb @@ -1,8 +1,8 @@ module OpenidConnect - module Authentication + module ProtectedResourceEndpoint def self.included(klass) - klass.send :include, Authentication::Helper + klass.send :include, ProtectedResourceEndpoint::Helper end module Helper diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb index 070ae4549..2f8e9c3d7 100644 --- a/lib/openid_connect/token_endpoint.rb +++ b/lib/openid_connect/token_endpoint.rb @@ -5,35 +5,43 @@ module OpenidConnect def initialize @app = Rack::OAuth2::Server::Token.new do |req, res| - case req.grant_type - when :password - user = User.find_for_database_authentication(username: req.username) - if user - o_auth_app = retrieveOrCreateNewClientApplication(req, user) - if o_auth_app && user.valid_password?(req.password) - res.access_token = o_auth_app.tokens.create!.bearer_token - else - req.invalid_grant! - end - else - req.invalid_grant! # TODO: Change to user login - end - else - res.unsupported_grant_type! + o_auth_app = retrieveClient(req) + if isAppValid(o_auth_app, req) + handleFlows(req, res) + else + req.invalid_client! end end end - def retrieveOrCreateNewClientApplication(req, user) - retrieveClient(req, user) || createClient(req, user) + def handleFlows(req, res) + case req.grant_type + when :password + handlePasswordFlow(req, res) + else + req.unsupported_grant_type! + end end - def retrieveClient(req, user) - user.o_auth_applications.find_by_client_id req.client_id + def handlePasswordFlow(req, res) + user = User.find_for_database_authentication(username: req.username) + if user + if user.valid_password?(req.password) + res.access_token = user.tokens.create!.bearer_token + else + req.invalid_grant! + end + else + req.invalid_grant! # TODO: Change to user login: Perhaps redirect_to login_path? + end end - def createClient(req, user) - user.o_auth_applications.create!(client_id: req.client_id, client_secret: req.client_secret) + def retrieveClient(req) + OAuthApplication.find_by_client_id req.client_id + end + + def isAppValid(o_auth_app, req) + o_auth_app.client_secret == req.client_secret end end end diff --git a/spec/controllers/openid_connect/clients_controller_spec.rb b/spec/controllers/openid_connect/clients_controller_spec.rb new file mode 100644 index 000000000..bce1f9295 --- /dev/null +++ b/spec/controllers/openid_connect/clients_controller_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe OpenidConnect::ClientsController, type: :controller do + describe "#create" do + context "when valid parameters are passed" do + it "should return a client id" do + post :create, + { + redirect_uris: ["http://localhost"] + } + clientJSON = JSON.parse(response.body) + expect(clientJSON["o_auth_application"]["client_id"].length).to eq(32) + end + end + context "when redirect uri is missing" do + it "should return a invalid_client_metadata error" do + post :create + clientJSON = JSON.parse(response.body) + expect(clientJSON["error"]).to have_content("invalid_client_metadata") + end + end + end +end diff --git a/spec/controllers/api/v0/users_controller_spec.rb b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb similarity index 63% rename from spec/controllers/api/v0/users_controller_spec.rb rename to spec/lib/openid_connect/protected_resource_endpoint_spec.rb index caa874f54..5a8251b0a 100644 --- a/spec/controllers/api/v0/users_controller_spec.rb +++ b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb @@ -1,20 +1,26 @@ require 'spec_helper' -describe Api::V0::UsersController, type: :request do - describe "#show" do - let!(:application) { bob.o_auth_applications.create!(client_id: 1, client_secret: "secret") } - let!(:token) { application.tokens.create!.bearer_token.to_s } +describe OpenidConnect::ProtectedResourceEndpoint, type: :request do + describe "getting the user info" do + let!(:token) { bob.tokens.create!.bearer_token.to_s } let(:invalid_token) { SecureRandom.hex(32).to_s } + # TODO: Add tests for expired access tokens - context "when valid" do + context "when access token is valid" do it "shows the user's username and email" do - get "/api/v0/user/?access_token=" + token + get "/api/v0/user/", + { + access_token: token + } jsonBody = JSON.parse(response.body) expect(jsonBody["username"]).to eq(bob.username) expect(jsonBody["email"]).to eq(bob.email) end it "should include private in the cache-control header" do - get "/api/v0/user/?access_token=" + token + get "/api/v0/user/", + { + access_token: token + } expect(response.headers["Cache-Control"]).to include("private") end end @@ -32,15 +38,24 @@ describe Api::V0::UsersController, type: :request do context "when an invalid access token is provided" do it "should respond with a 401 Unauthorized response" do - get "/api/v0/user/?access_token=" + invalid_token + get "/api/v0/user/", + { + access_token: invalid_token + } expect(response.status).to be(401) end it "should have an auth-scheme value of Bearer" do - get "/api/v0/user/?access_token=" + invalid_token + get "/api/v0/user/", + { + access_token: invalid_token + } expect(response.headers["WWW-Authenticate"]).to include("Bearer") end it "should contain an invalid_token error" do - get "/api/v0/user/?access_token=" + invalid_token + get "/api/v0/user/", + { + access_token: invalid_token + } expect(response.body).to include("invalid_token") end end diff --git a/spec/lib/openid_connect/token_endpoint_spec.rb b/spec/lib/openid_connect/token_endpoint_spec.rb index 4325a05e0..01de06161 100644 --- a/spec/lib/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/openid_connect/token_endpoint_spec.rb @@ -1,63 +1,114 @@ require 'spec_helper' describe OpenidConnect::TokenEndpoint, type: :request do - describe "password grant type" do + let!(:client) { OAuthApplication.create!(redirect_uris: ["http://localhost"]) } + describe "the password grant type" do context "when the username field is missing" do it "should return an invalid request error" do - post "/openid/access_tokens?grant_type=password\&password=bluepin7\&client_id=4\&client_secret=azerty" + post "/openid_connect/access_tokens", + { + grant_type: "password", + password: "bluepin7", + client_id: client.client_id, + client_secret: client.client_secret + } expect(response.body).to include("'username' required") end end context "when the password field is missing" do it "should return an invalid request error" do - post "/openid/access_tokens?grant_type=password\&username=bob\&client_id=4\&client_secret=azerty" + post "/openid_connect/access_tokens", + { + grant_type: "password", + username: "bob", + client_id: client.client_id, + client_secret: client.client_secret + } expect(response.body).to include("'password' required") end end context "when the username does not match an existing user" do it "should return an invalid request error" do - post "/openid/access_tokens?grant_type=password\&username=mewasdfrandom\&password=bluepin7\&client_id=4\&client_secret=azerty" + post "/openid_connect/access_tokens", + { + grant_type: "password", + username: "randomnoexist", + password: "bluepin7", + client_id: client.client_id, + client_secret: client.client_secret + } expect(response.body).to include("invalid_grant") end end context "when the password is invalid" do it "should return an invalid request error" do - post "/openid/access_tokens?grant_type=password\&username=mewasdfrandom\&password=bluepin7\&client_id=4\&client_secret=azerty" + post "/openid_connect/access_tokens", + { + grant_type: "password", + username: "bob", + password: "wrongpassword", + client_id: client.client_id, + client_secret: client.client_secret + } expect(response.body).to include("invalid_grant") end end - context "when there are duplicate fields" do - it "should return an invalid request error" do - post "/openid/access_tokens?grant_type=password\&username=bob\&password=bluepin6\&username=bob\&password=bluepin7\&client_id=4\&client_secret=azerty" - expect(response.body).to include("invalid_grant") - # TODO: Apparently Nov's implementation lets this one pass; however, according to the OIDC spec, we are supposed to reject duplicate fields. Is this a security issue? - end - end - context "when the client is unauthorized" do - # TODO: If we support password grant, we should prevent access from unauthorized client applications - it "should return an error" do - fail - end - end - context "when many unauthorized requests are made" do - # TODO: If we support password grant, we should support a way to prevent brute force attacks (using rate-limitation or generating alerts) as specified by RFC 6749 4.3.2 Access Token Request - it "should generate an alert" do - fail - end - end context "when the request is valid" do it "should return an access token" do - post "/openid/access_tokens?grant_type=password\&username=bob\&password=bluepin7\&client_id=4\&client_secret=azerty" + post "/openid_connect/access_tokens", + { + grant_type: "password", + username: "bob", + password: "bluepin7", + client_id: client.client_id, + client_secret: client.client_secret + } json = JSON.parse(response.body) expect(json["access_token"].length).to eq(64) expect(json["token_type"]).to eq("bearer") expect(json.keys).to include("expires_in") end end + context "when there are duplicate fields" do + it "should return an invalid request error" do + post "/openid_connect/access_tokens", + { + grant_type: "password", + username: "bob", + password: "bluepin7", + username: "bob", + password: "bluepin6", + client_id: client.client_id, + client_secret: client.client_secret + } + expect(response.body).to include("invalid_grant") + end + end + context "when the client is unregistered" do + it "should return an error" do + post "/openid_connect/access_tokens", + { + grant_type: "password", + username: "bob", + password: "bluepin7", + client_id: SecureRandom.hex(16).to_s, + client_secret: client.client_secret + } + expect(response.body).to include("invalid_client") + end + end + # TODO: Support a way to prevent brute force attacks using rate-limitation? as specified by RFC 6749 4.3.2 Access Token Request end - describe "unsupported grant type" do + describe "an unsupported grant type" do it "should return an unsupported grant type error" do - post "/openid/access_tokens?grant_type=me\&username=bob\&password=bluepin7\&client_id=4\&client_secret=azerty" + post "/openid_connect/access_tokens", + { + grant_type: "noexistgrant", + username: "bob", + password: "bluepin7", + client_id: client.client_id, + client_secret: client.client_secret + } expect(response.body).to include "unsupported_grant_type" end end From 059933f076d232d03f7cf5abbb6b33190c2f74d0 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 13 Sep 2015 14:17:02 -0700 Subject: [PATCH 011/105] Add scopes and authorization models --- app/models/authorization.rb | 7 +++++++ app/models/o_auth_application.rb | 2 ++ app/models/scope.rb | 8 ++++++++ app/models/token.rb | 1 + app/models/user.rb | 1 + db/migrate/20150708153926_create_authorizations.rb | 10 ++++++++++ db/migrate/20150708154727_create_scope.rb | 9 +++++++++ ...08155202_create_authorizations_scopes_join_table.rb | 8 ++++++++ .../20150708155747_create_scopes_tokens_join_table.rb | 8 ++++++++ 9 files changed, 54 insertions(+) create mode 100644 app/models/authorization.rb create mode 100644 app/models/scope.rb create mode 100644 db/migrate/20150708153926_create_authorizations.rb create mode 100644 db/migrate/20150708154727_create_scope.rb create mode 100644 db/migrate/20150708155202_create_authorizations_scopes_join_table.rb create mode 100644 db/migrate/20150708155747_create_scopes_tokens_join_table.rb diff --git a/app/models/authorization.rb b/app/models/authorization.rb new file mode 100644 index 000000000..157b4f77a --- /dev/null +++ b/app/models/authorization.rb @@ -0,0 +1,7 @@ +class Authorization < ActiveRecord::Base + belongs_to :user + belongs_to :o_auth_application + has_and_belongs_to_many :scopes + + # TODO: Incomplete class +end diff --git a/app/models/o_auth_application.rb b/app/models/o_auth_application.rb index f40816b32..5ac6b9e63 100644 --- a/app/models/o_auth_application.rb +++ b/app/models/o_auth_application.rb @@ -1,6 +1,8 @@ class OAuthApplication < ActiveRecord::Base belongs_to :user + has_many :authorizations + validates :client_id, presence: true, uniqueness: true validates :client_secret, presence: true diff --git a/app/models/scope.rb b/app/models/scope.rb new file mode 100644 index 000000000..a586fe631 --- /dev/null +++ b/app/models/scope.rb @@ -0,0 +1,8 @@ +class Scope < ActiveRecord::Base + has_and_belongs_to_many :tokens + has_and_belongs_to_many :authorizations + + validates :name, presence: true, uniqueness: true + + # TODO: Incomplete class +end diff --git a/app/models/token.rb b/app/models/token.rb index d466c71ca..da4aea70d 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -1,5 +1,6 @@ class Token < ActiveRecord::Base belongs_to :user + has_and_belongs_to_many :scopes before_validation :setup, on: :create diff --git a/app/models/user.rb b/app/models/user.rb index 1ab98f71a..6c98eeb73 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -77,6 +77,7 @@ class User < ActiveRecord::Base has_many :reports has_many :o_auth_applications + has_many :authorizations has_many :tokens before_save :guard_unconfirmed_email, diff --git a/db/migrate/20150708153926_create_authorizations.rb b/db/migrate/20150708153926_create_authorizations.rb new file mode 100644 index 000000000..572bdc83c --- /dev/null +++ b/db/migrate/20150708153926_create_authorizations.rb @@ -0,0 +1,10 @@ +class CreateAuthorizations < ActiveRecord::Migration + def change + create_table :authorizations do |t| + t.belongs_to :user, index: true + t.belongs_to :o_auth_application, index: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150708154727_create_scope.rb b/db/migrate/20150708154727_create_scope.rb new file mode 100644 index 000000000..8d2c51c02 --- /dev/null +++ b/db/migrate/20150708154727_create_scope.rb @@ -0,0 +1,9 @@ +class CreateScope < ActiveRecord::Migration + def change + create_table :scopes do |t| + t.string :name + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb b/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb new file mode 100644 index 000000000..c91e50d11 --- /dev/null +++ b/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb @@ -0,0 +1,8 @@ +class CreateAuthorizationsScopesJoinTable < ActiveRecord::Migration + def change + create_table :authorizations_scopes, id: false do |t| + t.belongs_to :authorization, index: true + t.belongs_to :scope, index: true + end + end +end diff --git a/db/migrate/20150708155747_create_scopes_tokens_join_table.rb b/db/migrate/20150708155747_create_scopes_tokens_join_table.rb new file mode 100644 index 000000000..b0416e5e8 --- /dev/null +++ b/db/migrate/20150708155747_create_scopes_tokens_join_table.rb @@ -0,0 +1,8 @@ +class CreateScopesTokensJoinTable < ActiveRecord::Migration + def change + create_table :scopes_tokens, id: false do |t| + t.belongs_to :scope, index: true + t.belongs_to :token, index: true + end + end +end From 3cfbcbce8fa87853075f72d0dff03d1eacf32331 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 10 Jul 2015 02:09:45 +0900 Subject: [PATCH 012/105] Implement authorization endpoint (part 1) The user can now authenticate with the authorization server's authorization endpoint and receive a fake id token. --- .../authorizations_controller.rb | 65 +++++++++-------- app/models/o_auth_application.rb | 6 ++ .../authorizations/_form.html.erb | 9 +++ .../authorizations/new.html.erb | 15 ++++ lib/openid_connect/authorization/endpoint.rb | 37 ++++------ .../endpoint_confirmation_point.rb | 35 ++++++++++ .../authorization/endpoint_start_point.rb | 28 ++++++++ .../authorizations_controller_spec.rb | 69 +++++++++++++++++++ 8 files changed, 210 insertions(+), 54 deletions(-) create mode 100644 app/views/openid_connect/authorizations/_form.html.erb create mode 100644 app/views/openid_connect/authorizations/new.html.erb create mode 100644 lib/openid_connect/authorization/endpoint_confirmation_point.rb create mode 100644 lib/openid_connect/authorization/endpoint_start_point.rb create mode 100644 spec/controllers/openid_connect/authorizations_controller_spec.rb diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index c10c137be..54f118715 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -1,47 +1,54 @@ -class AuthorizationsController < ApplicationController +class OpenidConnect::AuthorizationsController < ApplicationController rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| @error = e - logger.info e.backtrace[0,10].join("\n") - render :error, status: e.status + print e.backtrace[0,10].join("\n") + render json: {error: :error, status: e.status} #error_message: e.message end before_action :authenticate_user! def new - call_authorization_endpoint + request_authorization_consent_form end def create - call_authorization_endpoint :is_create, params[:approve] + process_authorization_consent(params[:approve]) end - private +private - def call_authorization_endpoint(is_create = false, approved = false) - endpoint = AuthorizationEndpoint.new is_create, approved - rack_response = *endpoint.call(request.env) - @client, @response_type, @redirect_uri, @scopes, @_request_, @request_uri, @request_object = *[ - endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint._request_, endpoint.request_uri, endpoint.request_object + def request_authorization_consent_form + endpoint = OpenidConnect::Authorization::EndpointStartPoint.new(current_user) + endpoint.call(request.env) + @client, @response_type, @redirect_uri, @scopes, @request_object = *[ + endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint.request_object ] - if ( - !is_create && - (max_age = @request_object.try(:id_token).try(:max_age)) && - current_account.last_logged_in_at < max_age.seconds.ago - ) - flash[:notice] = 'Exceeded Max Age, Login Again' - unauthenticate! - end - respond_as_rack_app *rack_response + saveRequestParameters + render :new end - def respond_as_rack_app(status, header, response) - ["WWW-Authenticate"].each do |key| - headers[key] = header[key] if header[key].present? - end - if response.redirect? - redirect_to header['Location'] - else - render :new - end + def process_authorization_consent(approvedString) + endpoint = OpenidConnect::Authorization::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString)) + restoreRequestParameters(endpoint) + status, header, response = *endpoint.call(request.env) + redirect_to header['Location'] + end + + def saveRequestParameters + session[:client_id], session[:response_type], session[:redirect_uri], session[:scopes], session[:request_object] = + @client.client_id, @response_type, @redirect_uri, @scopes.collect { |scope| scope.name }, @request_object + end + + def restoreRequestParameters(endpoint) + req = Rack::Request.new(request.env) + req.update_param("client_id", session[:client_id]) + req.update_param("redirect_uri", session[:redirect_uri]) + req.update_param("response_type", session[:response_type]) + endpoint.scopes, endpoint.request_object = + session[:scopes].collect {|scope| Scope.find_by_name(scope)}, session[:request_object] + end + + def to_boolean(str) + str.downcase == "true" end end diff --git a/app/models/o_auth_application.rb b/app/models/o_auth_application.rb index 5ac6b9e63..1f9a12ccb 100644 --- a/app/models/o_auth_application.rb +++ b/app/models/o_auth_application.rb @@ -6,6 +6,8 @@ class OAuthApplication < ActiveRecord::Base validates :client_id, presence: true, uniqueness: true validates :client_secret, presence: true + serialize :redirect_uris, JSON + before_validation :setup, on: :create def setup self.client_id = SecureRandom.hex(16) @@ -13,6 +15,10 @@ class OAuthApplication < ActiveRecord::Base end class << self + def available_response_types + ["id_token"] + end + def register!(registrarHash) registrarHash.validate! buildClientApplication(registrarHash) diff --git a/app/views/openid_connect/authorizations/_form.html.erb b/app/views/openid_connect/authorizations/_form.html.erb new file mode 100644 index 000000000..cce51e116 --- /dev/null +++ b/app/views/openid_connect/authorizations/_form.html.erb @@ -0,0 +1,9 @@ +<%= form_tag openid_connect_authorizations_path, class: action do %> + <% if action == :approve %> + <%= submit_tag "Approve" %> + <%= hidden_field_tag :approve, true %> + <% else %> + <%= submit_tag "Deny" %> + <%= hidden_field_tag :approve, false %> + <% end %> +<% end %> diff --git a/app/views/openid_connect/authorizations/new.html.erb b/app/views/openid_connect/authorizations/new.html.erb new file mode 100644 index 000000000..ee26ee058 --- /dev/null +++ b/app/views/openid_connect/authorizations/new.html.erb @@ -0,0 +1,15 @@ +

<%= @client.name %>

+

You will be redirected to <%= @redirect_uri %> with an id token if approved or an error if denied

+
    + <% @scopes.each do |scope| %> +
  • <%= scope.name %>
  • + <% end %> + <% if @request_object %> +
  • Request Objects (Currently not supported)
  • +
      +
      <%= JSON.pretty_generate @request_object.as_json %>
      +
    + <% end %> +
+<%= render 'openid_connect/authorizations/form', action: :approve %> +<%= render 'openid_connect/authorizations/form', action: :deny %> diff --git a/lib/openid_connect/authorization/endpoint.rb b/lib/openid_connect/authorization/endpoint.rb index cf2b359f3..885e17022 100644 --- a/lib/openid_connect/authorization/endpoint.rb +++ b/lib/openid_connect/authorization/endpoint.rb @@ -7,11 +7,7 @@ module OpenidConnect def initialize(current_user) @user = current_user @app = Rack::OAuth2::Server::Authorize.new do |req, res| - buildClient(req) - buildRedirectURI(req, res) - verifyNonce(req, res) - buildScopes(req) - buildRequestObject(req) + buildAttributes(req, res) if OAuthApplication.available_response_types.include? Array(req.response_type).collect(&:to_s).join(' ') handleResponseType(req, res) else @@ -19,32 +15,23 @@ module OpenidConnect end end end + def buildAttributes(req, res) + buildClient(req) + buildRedirectURI(req, res) + end + + def handleResponseType(req, res) + # Implemented by subclass + end + + private + def buildClient(req) @client = OAuthApplication.find_by_client_id(req.client_id) || req.bad_request! end def buildRedirectURI(req, res) res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@client.redirect_uris) end - def verifyNonce(req, res) - if res.protocol_params_location == :fragment && req.nonce.blank? - req.invalid_request! 'nonce required' - end - end - def buildScopes(req) - @scopes = req.scope.inject([]) do |_scopes_, scope| - _scopes_ << Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}" - end - end - def buildRequestObject(req) - @request_object = if (@_request_ = req.request).present? - OpenIDConnect::RequestObject.decode req.request, nil # @client.secret - elsif (@request_uri = req.request_uri).present? - OpenIDConnect::RequestObject.fetch req.request_uri, nil # @client.secret - end - end - def handleResponseType(req, res) - # Implemented by subclass - end end end end diff --git a/lib/openid_connect/authorization/endpoint_confirmation_point.rb b/lib/openid_connect/authorization/endpoint_confirmation_point.rb new file mode 100644 index 000000000..ff171e53e --- /dev/null +++ b/lib/openid_connect/authorization/endpoint_confirmation_point.rb @@ -0,0 +1,35 @@ +module OpenidConnect + module Authorization + class EndpointConfirmationPoint < Endpoint + def initialize(current_user, approved = false) + super(current_user) + @approved = approved + end + + def buildAttributes(req, res) + super(req, res) + # TODO: buildResponseType(req) + end + + def handleResponseType(req, res) + handleApproval(@approved, req, res) + end + + def handleApproval(approved, req, res) + if approved + approved!(req, res) + else + req.access_denied! + end + end + + def approved!(req, res) + response_types = Array(req.response_type) + if response_types.include?(:id_token) + res.id_token = SecureRandom.hex(16) # TODO: Replace with real ID token + end + res.approve! + end + end + end +end diff --git a/lib/openid_connect/authorization/endpoint_start_point.rb b/lib/openid_connect/authorization/endpoint_start_point.rb new file mode 100644 index 000000000..8fa6e4fa6 --- /dev/null +++ b/lib/openid_connect/authorization/endpoint_start_point.rb @@ -0,0 +1,28 @@ +module OpenidConnect + module Authorization + class EndpointStartPoint < Endpoint + def initialize(current_user) + super(current_user) + end + def handleResponseType(req, res) + @response_type = req.response_type + end + def buildAttributes(req, res) + super(req, res) + verifyNonce(req, res) + buildScopes(req) + # TODO: buildRequestObject(req) + end + def verifyNonce(req, res) + if res.protocol_params_location == :fragment && req.nonce.blank? + req.invalid_request! "nonce required" + end + end + def buildScopes(req) + @scopes = req.scope.inject([]) do |_scopes_, scope| + _scopes_ << Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}" + end + end + end + end +end diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb new file mode 100644 index 000000000..7852408ca --- /dev/null +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe OpenidConnect::AuthorizationsController, type: :controller do + let!(:client) { OAuthApplication.create!(redirect_uris: ["http://localhost:3000/"]) } + + before do + sign_in :user, alice + allow(@controller).to receive(:current_user).and_return(alice) + Scope.create!(name:"openid") + end + + describe "#new" do + render_views + context "when valid parameters are passed" do + it "should return a form page" do + get :new, + { + client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token", + scope: "openid", + nonce: SecureRandom.hex(16), + state: SecureRandom.hex(16) + } + expect(response.body).to match("Approve") + expect(response.body).to match("Deny") + end + end + # TODO: Implement tests for missing/invalid parameters + end + + describe "#create" do + before do + get :new, + { + client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token", + scope: "openid", + nonce: SecureRandom.hex(16), + state: SecureRandom.hex(16) + } + end + context "when authorization is approved" do + it "should return the id token in a fragment" do + post :create, + { + approve: "true" + } + expect(response.location).to have_content("#id_token=") + end + end + context "when authorization is denied" do + before do + post :create, + { + approve: "false" + } + end + it "should return an error in the fragment" do + expect(response.location).to have_content("#error=") + end + it "should NOT contain a id token in the fragment" do + expect(response.location).to_not have_content("#id_token=") + end + end + end + +end From 3d26cbf657aae8ca5fd3db557c0616d9e598bb2d Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 10 Jul 2015 15:12:21 +0900 Subject: [PATCH 013/105] Allow POST requests at authentication endpoint --- .../authorizations_controller.rb | 30 ++-- config/routes.rb | 5 +- .../authorization/endpoint_start_point.rb | 2 +- .../authorizations_controller_spec.rb | 129 ++++++++++++++++-- 4 files changed, 142 insertions(+), 24 deletions(-) diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index 54f118715..3b02cc625 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -1,8 +1,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| - @error = e - print e.backtrace[0,10].join("\n") - render json: {error: :error, status: e.status} #error_message: e.message + logger.info e.backtrace[0,10].join("\n") + render json: {error: e.message || :error, status: e.status} end before_action :authenticate_user! @@ -19,21 +18,34 @@ private def request_authorization_consent_form endpoint = OpenidConnect::Authorization::EndpointStartPoint.new(current_user) - endpoint.call(request.env) - @client, @response_type, @redirect_uri, @scopes, @request_object = *[ - endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint.request_object - ] - saveRequestParameters - render :new + handleStartPointResponse(endpoint) + end + + def handleStartPointResponse(endpoint) + status, header, response = *endpoint.call(request.env) + if response.redirect? + redirect_to header['Location'] + else + @client, @response_type, @redirect_uri, @scopes, @request_object = *[ + endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint.request_object + ] + saveRequestParameters + render :new + end end def process_authorization_consent(approvedString) endpoint = OpenidConnect::Authorization::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString)) restoreRequestParameters(endpoint) + handleConfirmationPointResponse(endpoint) + end + + def handleConfirmationPointResponse(endpoint) status, header, response = *endpoint.call(request.env) redirect_to header['Location'] end + def saveRequestParameters session[:client_id], session[:response_type], session[:redirect_uri], session[:scopes], session[:request_object] = @client.client_id, @response_type, @redirect_uri, @scopes.collect { |scope| scope.name }, @request_object diff --git a/config/routes.rb b/config/routes.rb index 520068e76..d1b1b1c7c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -236,8 +236,11 @@ Diaspora::Application.routes.draw do #OpenID Connect & OAuth namespace :openid_connect do resources :clients, only: :create - resources :authorizations, only: [:new, :create] post 'access_tokens', to: proc { |env| OpenidConnect::TokenEndpoint.new.call(env) } + + # Authorization Servers MUST support the use of the HTTP GET and POST methods at the Authorization Endpoint (see http://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation). + resources :authorizations, only: [:new, :create] + post 'authorizations/new', to: 'authorizations#new' end api_version(module: "Api::V0", path: {value: "api/v0"}, default: true) do diff --git a/lib/openid_connect/authorization/endpoint_start_point.rb b/lib/openid_connect/authorization/endpoint_start_point.rb index 8fa6e4fa6..d8fae10c4 100644 --- a/lib/openid_connect/authorization/endpoint_start_point.rb +++ b/lib/openid_connect/authorization/endpoint_start_point.rb @@ -20,7 +20,7 @@ module OpenidConnect end def buildScopes(req) @scopes = req.scope.inject([]) do |_scopes_, scope| - _scopes_ << Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}" + _scopes_ << (Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}") end end end diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb index 7852408ca..fedc62e60 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe OpenidConnect::AuthorizationsController, type: :controller do - let!(:client) { OAuthApplication.create!(redirect_uris: ["http://localhost:3000/"]) } + let!(:client) { OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) } + let!(:client_with_multiple_redirects) { OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/","http://localhost/"]) } before do sign_in :user, alice @@ -10,23 +11,120 @@ describe OpenidConnect::AuthorizationsController, type: :controller do end describe "#new" do - render_views context "when valid parameters are passed" do - it "should return a form page" do - get :new, + render_views + context "as GET request" do + it "should return a form page" do + get :new, + { + client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token", + scope: "openid", + nonce: SecureRandom.hex(16), + state: SecureRandom.hex(16) + } + expect(response.body).to match("Diaspora Test Client") + end + end + context "as POST request" do + it "should return a form page" do + post :new, + { + client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token", + scope: "openid", + nonce: SecureRandom.hex(16), + state: SecureRandom.hex(16) + } + expect(response.body).to match("Diaspora Test Client") + end + end + end + context "when client id is missing" do + it "should return an bad request error" do + post :new, { - client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) } - expect(response.body).to match("Approve") - expect(response.body).to match("Deny") + expect(response.body).to match("bad_request") + end + end + context "when redirect uri is missing" do + context "when only one redirect URL is pre-registered" do + it "should return a form pager" do + # Note this intentionally behavior diverts from OIDC spec http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + # See https://github.com/nov/rack-oauth2/blob/master/lib/rack/oauth2/server/authorize.rb#L63 + post :new, + { + client_id: client.client_id, + response_type: "id_token", + scope: "openid", + nonce: SecureRandom.hex(16), + state: SecureRandom.hex(16) + } + expect(response.body).to match("Diaspora Test Client") + end + end + end + context "when multiple redirect URLs are pre-registered" do + it "should return an invalid request error" do + post :new, + { + client_id: client_with_multiple_redirects.client_id, + response_type: "id_token", + scope: "openid", + nonce: SecureRandom.hex(16), + state: SecureRandom.hex(16) + } + expect(response.body).to match("bad_request") + end + end + context "when redirect URI does not match pre-registered URIs" do + it "should return an invalid request error" do + post :new, + { + client_id: client.client_id, + redirect_uri: "http://localhost:2000/", + response_type: "id_token", + scope: "openid", + nonce: SecureRandom.hex(16) + } + expect(response.body).to match("bad_request") + end + end + context "when an unsupported scope is passed in" do + it "should return an invalid scope error" do + post :new, + { + client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token", + scope: "random", + nonce: SecureRandom.hex(16), + state: SecureRandom.hex(16) + } + expect(response.body).to match("error=invalid_scope") + end + end + context "when nonce is missing" do + it "should return an invalid request error" do + post :new, + { + client_id: client.client_id, + redirect_uri: "http://localhost:3000/", + response_type: "id_token", + scope: "openid", + state: SecureRandom.hex(16) + } + expect(response.location).to match("error=invalid_request") end end - # TODO: Implement tests for missing/invalid parameters end describe "#create" do @@ -38,16 +136,21 @@ describe OpenidConnect::AuthorizationsController, type: :controller do response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16), - state: SecureRandom.hex(16) + state: 4180930983 } end context "when authorization is approved" do - it "should return the id token in a fragment" do + before do post :create, { approve: "true" } - expect(response.location).to have_content("#id_token=") + end + it "should return the id token in a fragment" do + expect(response.location).to have_content("id_token=") + end + it "should return the passed in state" do + expect(response.location).to have_content("state=4180930983") end end context "when authorization is denied" do @@ -58,10 +161,10 @@ describe OpenidConnect::AuthorizationsController, type: :controller do } end it "should return an error in the fragment" do - expect(response.location).to have_content("#error=") + expect(response.location).to have_content("error=") end it "should NOT contain a id token in the fragment" do - expect(response.location).to_not have_content("#id_token=") + expect(response.location).to_not have_content("id_token=") end end end From c6eb72251755ef13b573708233cce501e9099940 Mon Sep 17 00:00:00 2001 From: Augier Date: Sat, 11 Jul 2015 10:51:39 +0200 Subject: [PATCH 014/105] Replace ERB by HAML, added locales, corrected Gemfile --- .../openid_connect/authorizations/_form.haml | 7 +++++++ .../openid_connect/authorizations/_form.html.erb | 9 --------- .../openid_connect/authorizations/new.html.erb | 15 --------------- .../openid_connect/authorizations/new.html.haml | 14 ++++++++++++++ config/application.rb | 1 - config/locales/diaspora/en.yml | 9 +++++++++ 6 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 app/views/openid_connect/authorizations/_form.haml delete mode 100644 app/views/openid_connect/authorizations/_form.html.erb delete mode 100644 app/views/openid_connect/authorizations/new.html.erb create mode 100644 app/views/openid_connect/authorizations/new.html.haml diff --git a/app/views/openid_connect/authorizations/_form.haml b/app/views/openid_connect/authorizations/_form.haml new file mode 100644 index 000000000..9a72bff90 --- /dev/null +++ b/app/views/openid_connect/authorizations/_form.haml @@ -0,0 +1,7 @@ += form_tag openid_connect_authorizations_path, class: action do + - if action == :approve + = submit_tag t(".approve") + = hidden_field_tag :approve, true + - else + = submit_tag t(".deny") + = hidden_field_tag :approve, false diff --git a/app/views/openid_connect/authorizations/_form.html.erb b/app/views/openid_connect/authorizations/_form.html.erb deleted file mode 100644 index cce51e116..000000000 --- a/app/views/openid_connect/authorizations/_form.html.erb +++ /dev/null @@ -1,9 +0,0 @@ -<%= form_tag openid_connect_authorizations_path, class: action do %> - <% if action == :approve %> - <%= submit_tag "Approve" %> - <%= hidden_field_tag :approve, true %> - <% else %> - <%= submit_tag "Deny" %> - <%= hidden_field_tag :approve, false %> - <% end %> -<% end %> diff --git a/app/views/openid_connect/authorizations/new.html.erb b/app/views/openid_connect/authorizations/new.html.erb deleted file mode 100644 index ee26ee058..000000000 --- a/app/views/openid_connect/authorizations/new.html.erb +++ /dev/null @@ -1,15 +0,0 @@ -

<%= @client.name %>

-

You will be redirected to <%= @redirect_uri %> with an id token if approved or an error if denied

-
    - <% @scopes.each do |scope| %> -
  • <%= scope.name %>
  • - <% end %> - <% if @request_object %> -
  • Request Objects (Currently not supported)
  • -
      -
      <%= JSON.pretty_generate @request_object.as_json %>
      -
    - <% end %> -
-<%= render 'openid_connect/authorizations/form', action: :approve %> -<%= render 'openid_connect/authorizations/form', action: :deny %> diff --git a/app/views/openid_connect/authorizations/new.html.haml b/app/views/openid_connect/authorizations/new.html.haml new file mode 100644 index 000000000..31726c0fd --- /dev/null +++ b/app/views/openid_connect/authorizations/new.html.haml @@ -0,0 +1,14 @@ +%h2= @client.name +%p= t(".will_be_redirected") += @redirect_uri += t(".with_id_token") +%ul + - @scopes.each do |scope| + %li= scope.name + - if @request_object + %li= t(".requested_objects") + %ul + %pre= JSON.pretty_generate @request_object.as_json + += render 'openid_connect/authorizations/form', action: :approve += render 'openid_connect/authorizations/form', action: :deny diff --git a/config/application.rb b/config/application.rb index acb8eba06..3745fec5c 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,7 +31,6 @@ module Diaspora # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += %W{#{config.root}/app} config.autoload_once_paths += %W{#{config.root}/lib} - config.autoload_paths += %W{#{config.root}/lib/openid} # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 8a9440056..113180940 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -881,6 +881,15 @@ en: Hoping to see you again, The diaspora* email robot! + openid_connect: + authorizations: + new: + will_be_redirected: "You will be redirected to" + with_id_token: "with an id token if approved or an error if denied" + requested_objects: "Request Objects (Currently not supported)" + form: + approve: "Approve" + deny: "Deny" people: zero: "No people" one: "1 person" From 73cc55940df9fd466f3bbb1e9bc25612277b94fc Mon Sep 17 00:00:00 2001 From: Augier Date: Thu, 13 Aug 2015 13:20:22 +0900 Subject: [PATCH 015/105] Fix travis errors and refactor --- app/controllers/api/v0/users_controller.rb | 4 +- app/controllers/openid_connect/LICENSE | 22 --- .../authorizations_controller.rb | 33 +++-- .../openid_connect/clients_controller.rb | 31 +++-- .../openid_connect/discovery_controller.rb | 42 +++--- app/controllers/users_controller.rb | 3 +- app/models/authorization.rb | 2 +- app/models/authorization_scope.rb | 7 + app/models/o_auth_application.rb | 22 +-- app/models/scope.rb | 4 +- app/models/scopes_tokens.rb | 7 + app/models/token.rb | 6 +- config/application.rb | 2 +- config/routes.rb | 10 +- ...150613202109_create_o_auth_applications.rb | 2 +- features/step_definitions/openid_steps.rb | 64 ++++----- lib/account_deleter.rb | 3 +- lib/openid_connect/LICENSE | 22 --- lib/openid_connect/authorization/endpoint.rb | 25 ++-- .../endpoint_confirmation_point.rb | 10 +- .../authorization/endpoint_start_point.rb | 24 ++-- .../protected_resource_endpoint.rb | 1 - lib/openid_connect/token_endpoint.rb | 22 +-- .../api/v0/base_controller_spec.rb | 3 +- .../authorizations_controller_spec.rb | 129 ++++++------------ .../openid_connect/clients_controller_spec.rb | 15 +- .../protected_resource_endpoint_spec.rb | 33 ++--- .../lib/openid_connect/token_endpoint_spec.rb | 85 +++--------- spec/models/o_auth_application_spec.rb | 5 - spec/models/token_spec.rb | 5 - spec/presenters/api/v0/base_presenter_spec.rb | 3 +- spec/requests/api/v2/base_controller_spec.rb | 3 +- spec/spec_helper.rb | 8 -- 33 files changed, 237 insertions(+), 420 deletions(-) delete mode 100644 app/controllers/openid_connect/LICENSE create mode 100644 app/models/authorization_scope.rb create mode 100644 app/models/scopes_tokens.rb delete mode 100644 lib/openid_connect/LICENSE delete mode 100644 spec/models/o_auth_application_spec.rb delete mode 100644 spec/models/token_spec.rb diff --git a/app/controllers/api/v0/users_controller.rb b/app/controllers/api/v0/users_controller.rb index 60bd14589..c09f4cdb4 100644 --- a/app/controllers/api/v0/users_controller.rb +++ b/app/controllers/api/v0/users_controller.rb @@ -1,10 +1,10 @@ class Api::V0::UsersController < Api::V0::BaseController - def show render json: user end -private + private + def user current_token.user end diff --git a/app/controllers/openid_connect/LICENSE b/app/controllers/openid_connect/LICENSE deleted file mode 100644 index ace31447b..000000000 --- a/app/controllers/openid_connect/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -This code is based on https://github.com/nov/openid_connect_sample - -Copyright (c) 2011 nov matake - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index 3b02cc625..c16d0ae9c 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -1,7 +1,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| logger.info e.backtrace[0,10].join("\n") - render json: {error: e.message || :error, status: e.status} + render json: { error: e.message || :error, status: e.status } end before_action :authenticate_user! @@ -14,50 +14,49 @@ class OpenidConnect::AuthorizationsController < ApplicationController process_authorization_consent(params[:approve]) end -private + private def request_authorization_consent_form endpoint = OpenidConnect::Authorization::EndpointStartPoint.new(current_user) - handleStartPointResponse(endpoint) + handle_startpoint_response(endpoint) end - def handleStartPointResponse(endpoint) - status, header, response = *endpoint.call(request.env) + def handle_startpoint_response(endpoint) + _status, header, response = *endpoint.call(request.env) if response.redirect? - redirect_to header['Location'] + redirect_to header["Location"] else @client, @response_type, @redirect_uri, @scopes, @request_object = *[ endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint.request_object ] - saveRequestParameters + save_request_parameters render :new end end def process_authorization_consent(approvedString) endpoint = OpenidConnect::Authorization::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString)) - restoreRequestParameters(endpoint) - handleConfirmationPointResponse(endpoint) + restore_request_parameters(endpoint) + handle_confirmation_endpoint_response(endpoint) end - def handleConfirmationPointResponse(endpoint) - status, header, response = *endpoint.call(request.env) - redirect_to header['Location'] + def handle_confirmation_endpoint_response(endpoint) + _status, header, _response = *endpoint.call(request.env) + redirect_to header["Location"] end - - def saveRequestParameters + def save_request_parameters session[:client_id], session[:response_type], session[:redirect_uri], session[:scopes], session[:request_object] = - @client.client_id, @response_type, @redirect_uri, @scopes.collect { |scope| scope.name }, @request_object + @client.client_id, @response_type, @redirect_uri, @scopes.map(&:name), @request_object end - def restoreRequestParameters(endpoint) + def restore_request_parameters(endpoint) req = Rack::Request.new(request.env) req.update_param("client_id", session[:client_id]) req.update_param("redirect_uri", session[:redirect_uri]) req.update_param("response_type", session[:response_type]) endpoint.scopes, endpoint.request_object = - session[:scopes].collect {|scope| Scope.find_by_name(scope)}, session[:request_object] + session[:scopes].map {|scope| Scope.find_by_name(scope) }, session[:request_object] end def to_boolean(str) diff --git a/app/controllers/openid_connect/clients_controller.rb b/app/controllers/openid_connect/clients_controller.rb index e067b9ab2..bc28752e3 100644 --- a/app/controllers/openid_connect/clients_controller.rb +++ b/app/controllers/openid_connect/clients_controller.rb @@ -1,10 +1,10 @@ class OpenidConnect::ClientsController < ApplicationController - rescue_from OpenIDConnect::HttpError do |e| - rewriteHTTPErrorPageAsJSON(e) + http_error_page_as_json(e) end + rescue_from OpenIDConnect::ValidationFailed do |e| - rewriteValidationFailErrorPageAsJSON(e) + validation_fail_as_json(e) end def create @@ -13,18 +13,21 @@ class OpenidConnect::ClientsController < ApplicationController render json: client end -private + private - def rewriteHTTPErrorPageAsJSON(e) - render json: { - error: :invalid_request, - error_description: e.message - }, status: 400 + def http_error_page_as_json(e) + render json: + { + error: :invalid_request, + error_description: e.message + }, status: 400 end - def rewriteValidationFailErrorPageAsJSON(e) - render json: { - error: :invalid_client_metadata, - error_description: e.message - }, status: 400 + + def validation_fail_as_json(e) + render json: + { + error: :invalid_client_metadata, + error_description: e.message + }, status: 400 end end diff --git a/app/controllers/openid_connect/discovery_controller.rb b/app/controllers/openid_connect/discovery_controller.rb index 5b53e5c13..e9b0a80b5 100644 --- a/app/controllers/openid_connect/discovery_controller.rb +++ b/app/controllers/openid_connect/discovery_controller.rb @@ -1,12 +1,12 @@ class DiscoveryController < ApplicationController def show case params[:id] - when 'webfinger' - webfinger_discovery - when 'openid-configuration' - openid_configuration - else - raise HttpError::NotFound + when "webfinger" + webfinger_discovery + when "openid-configuration" + openid_configuration + else + raise HttpError::NotFound end end @@ -15,7 +15,7 @@ class DiscoveryController < ApplicationController def webfinger_discovery jrd = { links: [{ - rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, + rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, href: root_path }] } @@ -25,20 +25,20 @@ class DiscoveryController < ApplicationController def openid_configuration config = OpenIDConnect::Discovery::Provider::Config::Response.new( - issuer: root_path, - authorization_endpoint: "#{authorizations_url}/new", - token_endpoint: access_tokens_url, - userinfo_endpoint: user_info_url, - jwks_uri: "#{authorizations_url}/jwks.json", - registration_endpoint: "#{root_path}/connect", - scopes_supported: "iss", - response_types_supported: "Client.available_response_types", - grant_types_supported: "Client.available_grant_types", - request_object_signing_alg_values_supported: [:HS256, :HS384, :HS512], - subject_types_supported: ['public', 'pairwise'], - id_token_signing_alg_values_supported: [:RS256], - token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post'], - claims_supported: ['sub', 'iss', 'name', 'email'] + issuer: root_path, + authorization_endpoint: "#{authorizations_url}/new", + token_endpoint: access_tokens_url, + userinfo_endpoint: user_info_url, + jwks_uri: "#{authorizations_url}/jwks.json", + registration_endpoint: "#{root_path}/connect", + scopes_supported: "iss", + response_types_supported: "Client.available_response_types", + grant_types_supported: "Client.available_grant_types", + request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), + subject_types_supported: %w(public pairwise), + id_token_signing_alg_values_supported: %i(RS256), + token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), + claims_supported: %w(sub iss name email) ) render json: config end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 91376d45f..7bce10206 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,8 +3,7 @@ # the COPYRIGHT file. class UsersController < ApplicationController - - before_action :authenticate_user!, except: [:new, :create, :public, :user_photo] + before_action :authenticate_user!, except: %i(new create public user_photo) respond_to :html def edit diff --git a/app/models/authorization.rb b/app/models/authorization.rb index 157b4f77a..a94e9edf2 100644 --- a/app/models/authorization.rb +++ b/app/models/authorization.rb @@ -1,7 +1,7 @@ class Authorization < ActiveRecord::Base belongs_to :user belongs_to :o_auth_application - has_and_belongs_to_many :scopes + has_many :scopes, through: :authorization_scopes # TODO: Incomplete class end diff --git a/app/models/authorization_scope.rb b/app/models/authorization_scope.rb new file mode 100644 index 000000000..d8a88d779 --- /dev/null +++ b/app/models/authorization_scope.rb @@ -0,0 +1,7 @@ +class AuthorizationScope < ActiveRecord::Base + belongs_to authorization + belongs_to scope + + validates authorization, presence: true + validates scope, presence: true +end diff --git a/app/models/o_auth_application.rb b/app/models/o_auth_application.rb index 1f9a12ccb..0b6b2b9df 100644 --- a/app/models/o_auth_application.rb +++ b/app/models/o_auth_application.rb @@ -19,25 +19,13 @@ class OAuthApplication < ActiveRecord::Base ["id_token"] end - def register!(registrarHash) - registrarHash.validate! - buildClientApplication(registrarHash) + def register!(registrar) + registrar.validate! + build_client_application(registrar) end - def buildClientApplication(registrarHash) - client = OAuthApplication.create! - client.attributes = filterNilValues(registrarHash) - client.save! - client - end - - def filterNilValues(registrarHash) - { - name: registrarHash.client_name, - redirect_uris: registrarHash.redirect_uris - }.delete_if do |key, value| - value.nil? - end + def build_client_application(registrar) + create! redirect_uris: registrar.redirect_uris end end end diff --git a/app/models/scope.rb b/app/models/scope.rb index a586fe631..a2cccb2da 100644 --- a/app/models/scope.rb +++ b/app/models/scope.rb @@ -1,6 +1,6 @@ class Scope < ActiveRecord::Base - has_and_belongs_to_many :tokens - has_and_belongs_to_many :authorizations + has_many :tokens, through: :scope_tokens + has_many :authorizations, through: :authorization_scopes validates :name, presence: true, uniqueness: true diff --git a/app/models/scopes_tokens.rb b/app/models/scopes_tokens.rb new file mode 100644 index 000000000..82f3d1c5e --- /dev/null +++ b/app/models/scopes_tokens.rb @@ -0,0 +1,7 @@ +class ScopeToken < ActiveRecord::Base + belongs_to :scope + belongs_to :token + + validates :scope, presence: true + validates :token, presence: true +end diff --git a/app/models/token.rb b/app/models/token.rb index da4aea70d..7a36a3398 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -1,6 +1,6 @@ class Token < ActiveRecord::Base belongs_to :user - has_and_belongs_to_many :scopes + has_many :scopes, through: :scope_tokens before_validation :setup, on: :create @@ -16,11 +16,11 @@ class Token < ActiveRecord::Base def bearer_token @bearer_token ||= Rack::OAuth2::AccessToken::Bearer.new( access_token: token, - expires_in: (expires_at - Time.now.utc).to_i + expires_in: (expires_at - Time.now.utc).to_i ) end - def accessible?(_scopes_or_claims_ = nil) + def accessible?(_scopes_or_claims_=nil) true # TODO: For now don't support scopes end end diff --git a/config/application.rb b/config/application.rb index 3745fec5c..49ddc6b1b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -108,7 +108,7 @@ module Diaspora } config.action_mailer.asset_host = AppConfig.pod_uri.to_s - config.middleware.use Rack::OAuth2::Server::Resource::Bearer, 'OpenID Connect' do |req| + config.middleware.use Rack::OAuth2::Server::Resource::Bearer, "OpenID Connect" do |req| Token.valid(Time.now.utc).find_by(token: req.access_token) || req.invalid_token! end end diff --git a/config/routes.rb b/config/routes.rb index d1b1b1c7c..8920ef42a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -233,17 +233,17 @@ Diaspora::Application.routes.draw do # Startpage root :to => 'home#show' - #OpenID Connect & OAuth + # OpenID Connect & OAuth namespace :openid_connect do resources :clients, only: :create - post 'access_tokens', to: proc { |env| OpenidConnect::TokenEndpoint.new.call(env) } + post "access_tokens", to: proc {|env| OpenidConnect::TokenEndpoint.new.call(env) } # Authorization Servers MUST support the use of the HTTP GET and POST methods at the Authorization Endpoint (see http://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation). - resources :authorizations, only: [:new, :create] - post 'authorizations/new', to: 'authorizations#new' + resources :authorizations, only: %i(new create) + post "authorizations/new", to: "authorizations#new" end api_version(module: "Api::V0", path: {value: "api/v0"}, default: true) do - match 'user', to: 'users#show', via: [:get, :post] + match "user", to: "users#show", via: [:get, :post] end end diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index 8137f2b54..c5f44bfef 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -1,5 +1,5 @@ class CreateOAuthApplications < ActiveRecord::Migration - def self.up + def change create_table :o_auth_applications do |t| t.belongs_to :user, index: true t.string :client_id diff --git a/features/step_definitions/openid_steps.rb b/features/step_definitions/openid_steps.rb index a90cc35d3..3aa5bdf36 100644 --- a/features/step_definitions/openid_steps.rb +++ b/features/step_definitions/openid_steps.rb @@ -1,64 +1,46 @@ When /^I register a new client$/ do - clientRegistrationURL = "/openid_connect/clients" - post clientRegistrationURL, - { - redirect_uris: ["http://localhost:3000"] # Not actually used - } + client_registration_url = "/openid_connect/clients" + post client_registration_url, redirect_uris: ["http://localhost:3000"] # Not actually used end Given /^I send a post request from that client to the token endpoint using "([^\"]*)"'s credentials$/ do |username| - clientJSON = JSON.parse(last_response.body) + client_json = JSON.parse(last_response.body) user = User.find_by(username: username) - tokenEndpointURL = "/openid_connect/access_tokens" - post tokenEndpointURL, - { - grant_type: "password", - username: user.username, + token_endpoint_url = "/openid_connect/access_tokens" + post token_endpoint_url, grant_type: "password", username: user.username, password: "password", # Password has been hard coded as all test accounts seem to have a password of "password" - client_id: clientJSON["o_auth_application"]["client_id"], - client_secret: clientJSON["o_auth_application"]["client_secret"] - } + client_id: client_json["o_auth_application"]["client_id"], + client_secret: client_json["o_auth_application"]["client_secret"] end Given /^I send a post request from that client to the token endpoint using invalid credentials$/ do - clientJSON = JSON.parse(last_response.body) - tokenEndpointURL = "/openid_connect/access_tokens" - post tokenEndpointURL, - { - grant_type: "password", - username: User.find_by(username: "bob"), - password: "wrongpassword", - client_id: clientJSON["o_auth_application"]["client_id"], - client_secret: clientJSON["o_auth_application"]["client_secret"] - } + client_json = JSON.parse(last_response.body) + token_endpoint_url = "/openid_connect/access_tokens" + post token_endpoint_url, grant_type: "password", username: "bob", password: "wrongpassword", + client_id: client_json["o_auth_application"]["client_id"], + client_secret: client_json["o_auth_application"]["client_secret"] end When /^I use received valid bearer tokens to access user info$/ do - accessTokenJson = JSON.parse(last_response.body) - userInfoEndPointURL = "/api/v0/user/" - get userInfoEndPointURL, - { - access_token: accessTokenJson["access_token"] - } + access_token_json = JSON.parse(last_response.body) + user_info_endpoint_url = "/api/v0/user/" + get user_info_endpoint_url, access_token: access_token_json["access_token"] end When /^I use invalid bearer tokens to access user info$/ do - userInfoEndPointURL = "/api/v0/user/" - get userInfoEndPointURL, - { - access_token: SecureRandom.hex(32) - } + user_info_endpoint_url = "/api/v0/user/" + get user_info_endpoint_url, access_token: SecureRandom.hex(32) end Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username| - userInfoJson = JSON.parse(last_response.body) + user_info_json = JSON.parse(last_response.body) user = User.find_by_username(username) - expect(userInfoJson["username"]).to have_content(user.username) - expect(userInfoJson["language"]).to have_content(user.language) - expect(userInfoJson["email"]).to have_content(user.email) + expect(user_info_json["username"]).to have_content(user.username) + expect(user_info_json["language"]).to have_content(user.language) + expect(user_info_json["email"]).to have_content(user.email) end Then /^I should receive an "([^\"]*)" error$/ do |error_message| - userInfoJson = JSON.parse(last_response.body) - expect(userInfoJson["error"]).to have_content(error_message) + user_info_json = JSON.parse(last_response.body) + expect(user_info_json["error"]).to have_content(error_message) end diff --git a/lib/account_deleter.rb b/lib/account_deleter.rb index 7aaf40937..6018657cf 100644 --- a/lib/account_deleter.rb +++ b/lib/account_deleter.rb @@ -46,7 +46,8 @@ class AccountDeleter #user deletions def normal_ar_user_associates_to_delete - [:tag_followings, :invitations_to_me, :services, :aspects, :user_preferences, :notifications, :blocks] + %i(tag_followings invitations_to_me services aspects user_preferences + notifications blocks authorizations o_auth_applications tokens) end def special_ar_user_associations diff --git a/lib/openid_connect/LICENSE b/lib/openid_connect/LICENSE deleted file mode 100644 index ace31447b..000000000 --- a/lib/openid_connect/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -This code is based on https://github.com/nov/openid_connect_sample - -Copyright (c) 2011 nov matake - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/openid_connect/authorization/endpoint.rb b/lib/openid_connect/authorization/endpoint.rb index 885e17022..0d1adc5b1 100644 --- a/lib/openid_connect/authorization/endpoint.rb +++ b/lib/openid_connect/authorization/endpoint.rb @@ -1,35 +1,38 @@ module OpenidConnect module Authorization class Endpoint - attr_accessor :app, :user, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object + attr_accessor :app, :user, :client, :redirect_uri, :response_type, + :scopes, :_request_, :request_uri, :request_object delegate :call, to: :app def initialize(current_user) @user = current_user @app = Rack::OAuth2::Server::Authorize.new do |req, res| - buildAttributes(req, res) - if OAuthApplication.available_response_types.include? Array(req.response_type).collect(&:to_s).join(' ') - handleResponseType(req, res) + build_attributes(req, res) + if OAuthApplication.available_response_types.include? Array(req.response_type).map(&:to_s).join(" ") + handle_response_type(req, res) else req.unsupported_response_type! end end end - def buildAttributes(req, res) - buildClient(req) - buildRedirectURI(req, res) + + def build_attributes(req, res) + build_client(req) + build_redirect_uri(req, res) end - def handleResponseType(req, res) + def handle_response_type(req, res) # Implemented by subclass end - private + private - def buildClient(req) + def build_client(req) @client = OAuthApplication.find_by_client_id(req.client_id) || req.bad_request! end - def buildRedirectURI(req, res) + + def build_redirect_uri(req, res) res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@client.redirect_uris) end end diff --git a/lib/openid_connect/authorization/endpoint_confirmation_point.rb b/lib/openid_connect/authorization/endpoint_confirmation_point.rb index ff171e53e..1e8a61423 100644 --- a/lib/openid_connect/authorization/endpoint_confirmation_point.rb +++ b/lib/openid_connect/authorization/endpoint_confirmation_point.rb @@ -1,21 +1,21 @@ module OpenidConnect module Authorization class EndpointConfirmationPoint < Endpoint - def initialize(current_user, approved = false) + def initialize(current_user, approved=false) super(current_user) @approved = approved end - def buildAttributes(req, res) + def build_attributes(req, res) super(req, res) # TODO: buildResponseType(req) end - def handleResponseType(req, res) - handleApproval(@approved, req, res) + def handle_response_type(req, res) + handle_approval(@approved, req, res) end - def handleApproval(approved, req, res) + def handle_approval(approved, req, res) if approved approved!(req, res) else diff --git a/lib/openid_connect/authorization/endpoint_start_point.rb b/lib/openid_connect/authorization/endpoint_start_point.rb index d8fae10c4..5d44e7dc5 100644 --- a/lib/openid_connect/authorization/endpoint_start_point.rb +++ b/lib/openid_connect/authorization/endpoint_start_point.rb @@ -4,24 +4,30 @@ module OpenidConnect def initialize(current_user) super(current_user) end - def handleResponseType(req, res) + + def handle_response_type(req, res) @response_type = req.response_type end - def buildAttributes(req, res) + + def build_attributes(req, res) super(req, res) - verifyNonce(req, res) - buildScopes(req) + verify_nonce(req, res) + build_scopes(req) # TODO: buildRequestObject(req) end - def verifyNonce(req, res) + + def verify_nonce(req, res) if res.protocol_params_location == :fragment && req.nonce.blank? req.invalid_request! "nonce required" end end - def buildScopes(req) - @scopes = req.scope.inject([]) do |_scopes_, scope| - _scopes_ << (Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}") - end + + def build_scopes(req) + @scopes = req.scope.map {|scope| + Scope.where(name: scope).first.tap do |scope| + req.invalid_scope! "Unknown scope: #{scope}" unless scope + end + } end end end diff --git a/lib/openid_connect/protected_resource_endpoint.rb b/lib/openid_connect/protected_resource_endpoint.rb index fa840c18b..4b7a7ea9c 100644 --- a/lib/openid_connect/protected_resource_endpoint.rb +++ b/lib/openid_connect/protected_resource_endpoint.rb @@ -1,6 +1,5 @@ module OpenidConnect module ProtectedResourceEndpoint - def self.included(klass) klass.send :include, ProtectedResourceEndpoint::Helper end diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb index 2f8e9c3d7..a524de61d 100644 --- a/lib/openid_connect/token_endpoint.rb +++ b/lib/openid_connect/token_endpoint.rb @@ -5,25 +5,25 @@ module OpenidConnect def initialize @app = Rack::OAuth2::Server::Token.new do |req, res| - o_auth_app = retrieveClient(req) - if isAppValid(o_auth_app, req) - handleFlows(req, res) + o_auth_app = retrieve_client(req) + if app_valid?(o_auth_app, req) + handle_flows(req, res) else req.invalid_client! end end end - def handleFlows(req, res) + def handle_flows(req, res) case req.grant_type - when :password - handlePasswordFlow(req, res) - else - req.unsupported_grant_type! + when :password + handle_password_flow(req, res) + else + req.unsupported_grant_type! end end - def handlePasswordFlow(req, res) + def handle_password_flow(req, res) user = User.find_for_database_authentication(username: req.username) if user if user.valid_password?(req.password) @@ -36,11 +36,11 @@ module OpenidConnect end end - def retrieveClient(req) + def retrieve_client(req) OAuthApplication.find_by_client_id req.client_id end - def isAppValid(o_auth_app, req) + def app_valid?(o_auth_app, req) o_auth_app.client_secret == req.client_secret end end diff --git a/spec/controllers/api/v0/base_controller_spec.rb b/spec/controllers/api/v0/base_controller_spec.rb index f637d15b8..dc359bbe2 100644 --- a/spec/controllers/api/v0/base_controller_spec.rb +++ b/spec/controllers/api/v0/base_controller_spec.rb @@ -1,5 +1,4 @@ -require 'spec_helper' +require "spec_helper" describe Api::V0::BaseController do - end diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb index fedc62e60..974e0620b 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -1,13 +1,16 @@ -require 'spec_helper' +require "spec_helper" describe OpenidConnect::AuthorizationsController, type: :controller do let!(:client) { OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) } - let!(:client_with_multiple_redirects) { OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/","http://localhost/"]) } + let!(:client_with_multiple_redirects) do + OAuthApplication.create!( + name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/", "http://localhost/"]) + end before do sign_in :user, alice allow(@controller).to receive(:current_user).and_return(alice) - Scope.create!(name:"openid") + Scope.create!(name: "openid") end describe "#new" do @@ -15,113 +18,71 @@ describe OpenidConnect::AuthorizationsController, type: :controller do render_views context "as GET request" do it "should return a form page" do - get :new, - { - client_id: client.client_id, - redirect_uri: "http://localhost:3000/", - response_type: "id_token", - scope: "openid", - nonce: SecureRandom.hex(16), - state: SecureRandom.hex(16) - } + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) expect(response.body).to match("Diaspora Test Client") end end + context "as POST request" do it "should return a form page" do - post :new, - { - client_id: client.client_id, - redirect_uri: "http://localhost:3000/", - response_type: "id_token", - scope: "openid", - nonce: SecureRandom.hex(16), - state: SecureRandom.hex(16) - } + post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) expect(response.body).to match("Diaspora Test Client") end end end + context "when client id is missing" do it "should return an bad request error" do - post :new, - { - redirect_uri: "http://localhost:3000/", - response_type: "id_token", - scope: "openid", - nonce: SecureRandom.hex(16), - state: SecureRandom.hex(16) - } + post :new, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) expect(response.body).to match("bad_request") end end + context "when redirect uri is missing" do context "when only one redirect URL is pre-registered" do it "should return a form pager" do # Note this intentionally behavior diverts from OIDC spec http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + # When client has only one redirect uri registered, only that redirect uri can be used. Hence, + # we should implicitly assume the client wants to use that registered URI. # See https://github.com/nov/rack-oauth2/blob/master/lib/rack/oauth2/server/authorize.rb#L63 - post :new, - { - client_id: client.client_id, - response_type: "id_token", - scope: "openid", - nonce: SecureRandom.hex(16), - state: SecureRandom.hex(16) - } + post :new, client_id: client.client_id, response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) expect(response.body).to match("Diaspora Test Client") end end end + context "when multiple redirect URLs are pre-registered" do it "should return an invalid request error" do - post :new, - { - client_id: client_with_multiple_redirects.client_id, - response_type: "id_token", - scope: "openid", - nonce: SecureRandom.hex(16), - state: SecureRandom.hex(16) - } + post :new, client_id: client_with_multiple_redirects.client_id, response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) expect(response.body).to match("bad_request") end end + context "when redirect URI does not match pre-registered URIs" do it "should return an invalid request error" do - post :new, - { - client_id: client.client_id, - redirect_uri: "http://localhost:2000/", - response_type: "id_token", - scope: "openid", - nonce: SecureRandom.hex(16) - } + post :new, client_id: client.client_id, redirect_uri: "http://localhost:2000/", + response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16) expect(response.body).to match("bad_request") end end + context "when an unsupported scope is passed in" do it "should return an invalid scope error" do - post :new, - { - client_id: client.client_id, - redirect_uri: "http://localhost:3000/", - response_type: "id_token", - scope: "random", - nonce: SecureRandom.hex(16), - state: SecureRandom.hex(16) - } + post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "random", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) expect(response.body).to match("error=invalid_scope") end end + context "when nonce is missing" do it "should return an invalid request error" do - post :new, - { - client_id: client.client_id, - redirect_uri: "http://localhost:3000/", - response_type: "id_token", - scope: "openid", - state: SecureRandom.hex(16) - } + post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", state: SecureRandom.hex(16) expect(response.location).to match("error=invalid_request") end end @@ -129,44 +90,36 @@ describe OpenidConnect::AuthorizationsController, type: :controller do describe "#create" do before do - get :new, - { - client_id: client.client_id, - redirect_uri: "http://localhost:3000/", - response_type: "id_token", - scope: "openid", - nonce: SecureRandom.hex(16), - state: 4180930983 - } + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: 418_093_098_3 end + context "when authorization is approved" do before do - post :create, - { - approve: "true" - } + post :create, approve: "true" end + it "should return the id token in a fragment" do expect(response.location).to have_content("id_token=") end + it "should return the passed in state" do expect(response.location).to have_content("state=4180930983") end end + context "when authorization is denied" do before do - post :create, - { - approve: "false" - } + post :create, approve: "false" end + it "should return an error in the fragment" do expect(response.location).to have_content("error=") end + it "should NOT contain a id token in the fragment" do expect(response.location).to_not have_content("id_token=") end end end - end diff --git a/spec/controllers/openid_connect/clients_controller_spec.rb b/spec/controllers/openid_connect/clients_controller_spec.rb index bce1f9295..1f718e408 100644 --- a/spec/controllers/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/openid_connect/clients_controller_spec.rb @@ -1,22 +1,19 @@ -require 'spec_helper' +require "spec_helper" describe OpenidConnect::ClientsController, type: :controller do describe "#create" do context "when valid parameters are passed" do it "should return a client id" do - post :create, - { - redirect_uris: ["http://localhost"] - } - clientJSON = JSON.parse(response.body) - expect(clientJSON["o_auth_application"]["client_id"].length).to eq(32) + post :create, redirect_uris: ["http://localhost"] + client_json = JSON.parse(response.body) + expect(client_json["o_auth_application"]["client_id"].length).to eq(32) end end context "when redirect uri is missing" do it "should return a invalid_client_metadata error" do post :create - clientJSON = JSON.parse(response.body) - expect(clientJSON["error"]).to have_content("invalid_client_metadata") + client_json = JSON.parse(response.body) + expect(client_json["error"]).to have_content("invalid_client_metadata") end end end diff --git a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb index 5a8251b0a..247df0661 100644 --- a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require "spec_helper" describe OpenidConnect::ProtectedResourceEndpoint, type: :request do describe "getting the user info" do @@ -8,19 +8,13 @@ describe OpenidConnect::ProtectedResourceEndpoint, type: :request do context "when access token is valid" do it "shows the user's username and email" do - get "/api/v0/user/", - { - access_token: token - } - jsonBody = JSON.parse(response.body) - expect(jsonBody["username"]).to eq(bob.username) - expect(jsonBody["email"]).to eq(bob.email) + get "/api/v0/user/", access_token: token + json_body = JSON.parse(response.body) + expect(json_body["username"]).to eq(bob.username) + expect(json_body["email"]).to eq(bob.email) end it "should include private in the cache-control header" do - get "/api/v0/user/", - { - access_token: token - } + get "/api/v0/user/", access_token: token expect(response.headers["Cache-Control"]).to include("private") end end @@ -38,24 +32,15 @@ describe OpenidConnect::ProtectedResourceEndpoint, type: :request do context "when an invalid access token is provided" do it "should respond with a 401 Unauthorized response" do - get "/api/v0/user/", - { - access_token: invalid_token - } + get "/api/v0/user/", access_token: invalid_token expect(response.status).to be(401) end it "should have an auth-scheme value of Bearer" do - get "/api/v0/user/", - { - access_token: invalid_token - } + get "/api/v0/user/", access_token: invalid_token expect(response.headers["WWW-Authenticate"]).to include("Bearer") end it "should contain an invalid_token error" do - get "/api/v0/user/", - { - access_token: invalid_token - } + get "/api/v0/user/", access_token: invalid_token expect(response.body).to include("invalid_token") end end diff --git a/spec/lib/openid_connect/token_endpoint_spec.rb b/spec/lib/openid_connect/token_endpoint_spec.rb index 01de06161..b0d36aafe 100644 --- a/spec/lib/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/openid_connect/token_endpoint_spec.rb @@ -1,68 +1,40 @@ -require 'spec_helper' +require "spec_helper" describe OpenidConnect::TokenEndpoint, type: :request do let!(:client) { OAuthApplication.create!(redirect_uris: ["http://localhost"]) } describe "the password grant type" do context "when the username field is missing" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", - { - grant_type: "password", - password: "bluepin7", - client_id: client.client_id, - client_secret: client.client_secret - } + post "/openid_connect/access_tokens", grant_type: "password", password: "bluepin7", + client_id: client.client_id, client_secret: client.client_secret expect(response.body).to include("'username' required") end end context "when the password field is missing" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", - { - grant_type: "password", - username: "bob", - client_id: client.client_id, - client_secret: client.client_secret - } + post "/openid_connect/access_tokens", grant_type: "password", username: "bob", + client_id: client.client_id, client_secret: client.client_secret expect(response.body).to include("'password' required") end end context "when the username does not match an existing user" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", - { - grant_type: "password", - username: "randomnoexist", - password: "bluepin7", - client_id: client.client_id, - client_secret: client.client_secret - } + post "/openid_connect/access_tokens", grant_type: "password", username: "randomnoexist", + password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret expect(response.body).to include("invalid_grant") end end context "when the password is invalid" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", - { - grant_type: "password", - username: "bob", - password: "wrongpassword", - client_id: client.client_id, - client_secret: client.client_secret - } + post "/openid_connect/access_tokens", grant_type: "password", username: "bob", + password: "wrongpassword", client_id: client.client_id, client_secret: client.client_secret expect(response.body).to include("invalid_grant") end end context "when the request is valid" do it "should return an access token" do - post "/openid_connect/access_tokens", - { - grant_type: "password", - username: "bob", - password: "bluepin7", - client_id: client.client_id, - client_secret: client.client_secret - } + post "/openid_connect/access_tokens", grant_type: "password", username: "bob", + password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret json = JSON.parse(response.body) expect(json["access_token"].length).to eq(64) expect(json["token_type"]).to eq("bearer") @@ -71,44 +43,25 @@ describe OpenidConnect::TokenEndpoint, type: :request do end context "when there are duplicate fields" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", - { - grant_type: "password", - username: "bob", - password: "bluepin7", - username: "bob", - password: "bluepin6", - client_id: client.client_id, - client_secret: client.client_secret - } + post "/openid_connect/access_tokens", grant_type: "password", username: "bob", password: "bluepin7", + username: "bob", password: "bluepin6", client_id: client.client_id, client_secret: client.client_secret expect(response.body).to include("invalid_grant") end end context "when the client is unregistered" do it "should return an error" do - post "/openid_connect/access_tokens", - { - grant_type: "password", - username: "bob", - password: "bluepin7", - client_id: SecureRandom.hex(16).to_s, - client_secret: client.client_secret - } + post "/openid_connect/access_tokens", grant_type: "password", username: "bob", + password: "bluepin7", client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret expect(response.body).to include("invalid_client") end end - # TODO: Support a way to prevent brute force attacks using rate-limitation? as specified by RFC 6749 4.3.2 Access Token Request + # TODO: Support a way to prevent brute force attacks using rate-limitation + # as specified by RFC 6749 4.3.2 Access Token Request end describe "an unsupported grant type" do it "should return an unsupported grant type error" do - post "/openid_connect/access_tokens", - { - grant_type: "noexistgrant", - username: "bob", - password: "bluepin7", - client_id: client.client_id, - client_secret: client.client_secret - } + post "/openid_connect/access_tokens", grant_type: "noexistgrant", username: "bob", + password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret expect(response.body).to include "unsupported_grant_type" end end diff --git a/spec/models/o_auth_application_spec.rb b/spec/models/o_auth_application_spec.rb deleted file mode 100644 index 1f0bb9ef7..000000000 --- a/spec/models/o_auth_application_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe OAuthApplication, type: :model do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/token_spec.rb b/spec/models/token_spec.rb deleted file mode 100644 index 18bba17d4..000000000 --- a/spec/models/token_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe Token, type: :model do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/presenters/api/v0/base_presenter_spec.rb b/spec/presenters/api/v0/base_presenter_spec.rb index 36d38fbe7..01261461a 100644 --- a/spec/presenters/api/v0/base_presenter_spec.rb +++ b/spec/presenters/api/v0/base_presenter_spec.rb @@ -1,5 +1,4 @@ -require 'spec_helper' +require "spec_helper" describe Api::V0::BasePresenter do - end diff --git a/spec/requests/api/v2/base_controller_spec.rb b/spec/requests/api/v2/base_controller_spec.rb index f637d15b8..dc359bbe2 100644 --- a/spec/requests/api/v2/base_controller_spec.rb +++ b/spec/requests/api/v2/base_controller_spec.rb @@ -1,5 +1,4 @@ -require 'spec_helper' +require "spec_helper" describe Api::V0::BaseController do - end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index dcdb16d05..ca444256a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -59,14 +59,6 @@ def photo_fixture_name @photo_fixture_name = File.join(File.dirname(__FILE__), "fixtures", "button.png") end -def retrieveAccessToken(user) - o_auth_app = OAuthApplication.create!(client_id: 4, client_secret: "azerty") - user = User.find_for_database_authentication(username: user.username) - if o_auth_app && user && user.valid_password?("bluepin7") # Hard coded password for bob - o_auth_app.tokens.create!.bearer_token.to_s - end -end - # Force fixture rebuild FileUtils.rm_f(Rails.root.join("tmp", "fixture_builder.yml")) From 9d9dc1327265f63967dbf896e04d6c09ee005d34 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Mon, 13 Jul 2015 01:37:10 +0900 Subject: [PATCH 016/105] Adjust discovery controller to current values --- .../openid_connect/discovery_controller.rb | 71 ++++++++----------- config/routes.rb | 8 ++- .../discovery_controller_spec.rb | 27 +++++++ 3 files changed, 62 insertions(+), 44 deletions(-) create mode 100644 spec/controllers/openid_connect/discovery_controller_spec.rb diff --git a/app/controllers/openid_connect/discovery_controller.rb b/app/controllers/openid_connect/discovery_controller.rb index e9b0a80b5..020c05c2f 100644 --- a/app/controllers/openid_connect/discovery_controller.rb +++ b/app/controllers/openid_connect/discovery_controller.rb @@ -1,45 +1,32 @@ -class DiscoveryController < ApplicationController - def show - case params[:id] - when "webfinger" - webfinger_discovery - when "openid-configuration" - openid_configuration - else - raise HttpError::NotFound +module OpenidConnect + class DiscoveryController < ApplicationController + def webfinger + jrd = { + links: [{ + rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, + href: File.join(root_url, "openid_connect") + }] + } + jrd[:subject] = params[:resource] if params[:resource].present? + render json: jrd, content_type: "application/jrd+json" + end + + def configuration + render json: OpenIDConnect::Discovery::Provider::Config::Response.new( + issuer: root_url, + registration_endpoint: openid_connect_clients_url, + authorization_endpoint: new_openid_connect_authorization_url, + token_endpoint: openid_connect_access_tokens_url, + userinfo_endpoint: api_v0_user_url, + jwks_uri: "https://not_configured_yet.com", # TODO: File.join({new_openid_connect_authorization_path} + "/jwks.json"), + scopes_supported: Scope.pluck(:name), + response_types_supported: OAuthApplication.available_response_types, + request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), + subject_types_supported: %w(public pairwise), + id_token_signing_alg_values_supported: %i(RS256), + token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), + # TODO: claims_supported: ["sub", "iss", "name", "email"] + ) end end - - private - - def webfinger_discovery - jrd = { - links: [{ - rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, - href: root_path - }] - } - jrd[:subject] = params[:resource] if params[:resource].present? - render json: jrd, content_type: "application/jrd+json" - end - - def openid_configuration - config = OpenIDConnect::Discovery::Provider::Config::Response.new( - issuer: root_path, - authorization_endpoint: "#{authorizations_url}/new", - token_endpoint: access_tokens_url, - userinfo_endpoint: user_info_url, - jwks_uri: "#{authorizations_url}/jwks.json", - registration_endpoint: "#{root_path}/connect", - scopes_supported: "iss", - response_types_supported: "Client.available_response_types", - grant_types_supported: "Client.available_grant_types", - request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), - subject_types_supported: %w(public pairwise), - id_token_signing_alg_values_supported: %i(RS256), - token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), - claims_supported: %w(sub iss name email) - ) - render json: config - end end diff --git a/config/routes.rb b/config/routes.rb index 8920ef42a..79021f7cd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -238,12 +238,16 @@ Diaspora::Application.routes.draw do resources :clients, only: :create post "access_tokens", to: proc {|env| OpenidConnect::TokenEndpoint.new.call(env) } - # Authorization Servers MUST support the use of the HTTP GET and POST methods at the Authorization Endpoint (see http://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation). + # Authorization Servers MUST support the use of the HTTP GET and POST methods at the Authorization Endpoint + # See http://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation resources :authorizations, only: %i(new create) post "authorizations/new", to: "authorizations#new" + + get ".well-known/webfinger", to: "discovery#webfinger" + get ".well-known/openid-configuration", to: "discovery#configuration" end api_version(module: "Api::V0", path: {value: "api/v0"}, default: true) do - match "user", to: "users#show", via: [:get, :post] + match "user", to: "users#show", via: %i(get post) end end diff --git a/spec/controllers/openid_connect/discovery_controller_spec.rb b/spec/controllers/openid_connect/discovery_controller_spec.rb new file mode 100644 index 000000000..6a30d9c32 --- /dev/null +++ b/spec/controllers/openid_connect/discovery_controller_spec.rb @@ -0,0 +1,27 @@ +require "spec_helper" + +describe OpenidConnect::DiscoveryController, type: :controller do + describe "#webfinger" do + before do + get :webfinger, resource: "http://test.host/bob" + end + + it "should return a url to the openid-configuration" do + json_body = JSON.parse(response.body) + expect(json_body["links"].first["href"]).to eq("http://test.host/openid_connect") + end + + it "should return the resource in the subject" do + json_body = JSON.parse(response.body) + expect(json_body["subject"]).to eq("http://test.host/bob") + end + end + + describe "#configuration" do + it "should have the issuer as the root url" do + get :configuration + json_body = JSON.parse(response.body) + expect(json_body["issuer"]).to eq("http://test.host/") + end + end +end From 9140c8244ba38d9cb3cf3a53bfa55d393d726045 Mon Sep 17 00:00:00 2001 From: Augier Date: Mon, 13 Jul 2015 15:24:34 +0200 Subject: [PATCH 017/105] Support for refresh tokens w/ no tests --- app/models/refresh_token.rb | 26 +++++++++++++++++++ app/models/token.rb | 2 ++ .../20150713132035_create_refresh_token.rb | 14 ++++++++++ lib/openid_connect/token_endpoint.rb | 17 +++++++++++- 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 app/models/refresh_token.rb create mode 100644 db/migrate/20150713132035_create_refresh_token.rb diff --git a/app/models/refresh_token.rb b/app/models/refresh_token.rb new file mode 100644 index 000000000..08fcc6f47 --- /dev/null +++ b/app/models/refresh_token.rb @@ -0,0 +1,26 @@ +class RefreshToken < ActiveRecord::Base + belongs_to :token + + before_validation :setup, on: :create + + validates :refresh_token, presence: true, uniqueness: true + + attr_reader :refresh_token + + def setup + self.refresh_token = SecureRandom.hex(32) + # No expipration date for now + end + + # Finds the requested refresh token and destroys it if found; returns true if found, false otherwise + def valid?(token) + the_token = RefreshToken.find_by_refresh_token token + if the_token + RefreshToken.destroy_all refresh_token: the_token.refresh_token + Token.destroy_all refresh_token: the_token.refresh_token + true + else + false + end + end +end diff --git a/app/models/token.rb b/app/models/token.rb index 7a36a3398..e50796a38 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -1,6 +1,7 @@ class Token < ActiveRecord::Base belongs_to :user has_many :scopes, through: :scope_tokens + has_one :refresh_token before_validation :setup, on: :create @@ -10,6 +11,7 @@ class Token < ActiveRecord::Base def setup self.token = SecureRandom.hex(32) + self.refresh_token = RefreshToken.create! self.expires_at = 24.hours.from_now end diff --git a/db/migrate/20150713132035_create_refresh_token.rb b/db/migrate/20150713132035_create_refresh_token.rb new file mode 100644 index 000000000..e03632e12 --- /dev/null +++ b/db/migrate/20150713132035_create_refresh_token.rb @@ -0,0 +1,14 @@ +class RefreshToken < ActiveRecord::Migration + def change + create_table :refresh_token do + t.belongs_to :token + t.string :refresh_token + + t.timestamps null: false + end + end + + def self.down + drop_table :refresh_token + end +end diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb index a524de61d..197d9021c 100644 --- a/lib/openid_connect/token_endpoint.rb +++ b/lib/openid_connect/token_endpoint.rb @@ -18,6 +18,8 @@ module OpenidConnect case req.grant_type when :password handle_password_flow(req, res) + when :refresh_token + handle_refresh_flow(req, res) else req.unsupported_grant_type! end @@ -27,7 +29,7 @@ module OpenidConnect user = User.find_for_database_authentication(username: req.username) if user if user.valid_password?(req.password) - res.access_token = user.tokens.create!.bearer_token + res.access_token = token! user else req.invalid_grant! end @@ -36,6 +38,15 @@ module OpenidConnect end end + def handle_refresh_flow(req, res) + user = OAuthApplication.find_by_client_id(req.client_id).user + if RefreshToken.valid?(req.refresh_token) + res.access_token = token! user + else + req.invalid_grant! + end + end + def retrieve_client(req) OAuthApplication.find_by_client_id req.client_id end @@ -43,5 +54,9 @@ module OpenidConnect def app_valid?(o_auth_app, req) o_auth_app.client_secret == req.client_secret end + + def token!(user) + user.tokens.create!.bearer_token + end end end From 031679762a5b519fe708b3853cee40420b0bea00 Mon Sep 17 00:00:00 2001 From: augier Date: Sun, 13 Sep 2015 14:44:22 -0700 Subject: [PATCH 018/105] Redesign the models --- .../authorizations_controller.rb | 6 +- .../openid_connect/clients_controller.rb | 2 +- .../openid_connect/discovery_controller.rb | 56 +++++++++---------- app/models/authorization.rb | 7 --- app/models/openid_connect/authorization.rb | 43 ++++++++++++++ .../authorization_scope.rb | 0 .../o_auth_access_token.rb} | 5 +- .../o_auth_application.rb | 4 +- app/models/{ => openid_connect}/scope.rb | 2 +- .../{ => openid_connect}/scopes_tokens.rb | 2 +- app/models/refresh_token.rb | 26 --------- app/models/user.rb | 22 ++++---- config/application.rb | 2 +- ...50614134031_create_o_auth_access_tokens.rb | 13 +++++ db/migrate/20150614134031_create_tokens.rb | 15 ----- .../20150708153926_create_authorizations.rb | 1 + ...create_authorizations_scopes_join_table.rb | 2 +- ...8155747_create_scopes_tokens_join_table.rb | 2 +- .../20150713132035_create_refresh_token.rb | 14 ----- db/schema.rb | 56 +++++++++++++++---- lib/account_deleter.rb | 7 ++- .../{authorization => endpoints}/endpoint.rb | 7 ++- .../endpoint_confirmation_point.rb | 2 +- .../endpoint_start_point.rb | 4 +- lib/openid_connect/token_endpoint.rb | 14 ++--- .../authorizations_controller_spec.rb | 6 +- .../protected_resource_endpoint_spec.rb | 6 +- .../lib/openid_connect/token_endpoint_spec.rb | 2 +- 28 files changed, 181 insertions(+), 147 deletions(-) delete mode 100644 app/models/authorization.rb create mode 100644 app/models/openid_connect/authorization.rb rename app/models/{ => openid_connect}/authorization_scope.rb (100%) rename app/models/{token.rb => openid_connect/o_auth_access_token.rb} (84%) rename app/models/{ => openid_connect}/o_auth_application.rb (85%) rename app/models/{ => openid_connect}/scope.rb (79%) rename app/models/{ => openid_connect}/scopes_tokens.rb (68%) delete mode 100644 app/models/refresh_token.rb create mode 100644 db/migrate/20150614134031_create_o_auth_access_tokens.rb delete mode 100644 db/migrate/20150614134031_create_tokens.rb delete mode 100644 db/migrate/20150713132035_create_refresh_token.rb rename lib/openid_connect/{authorization => endpoints}/endpoint.rb (77%) rename lib/openid_connect/{authorization => endpoints}/endpoint_confirmation_point.rb (97%) rename lib/openid_connect/{authorization => endpoints}/endpoint_start_point.rb (89%) diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index c16d0ae9c..e5f39e8b0 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -17,7 +17,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController private def request_authorization_consent_form - endpoint = OpenidConnect::Authorization::EndpointStartPoint.new(current_user) + endpoint = OpenidConnect::Endpoints::EndpointStartPoint.new(current_user) handle_startpoint_response(endpoint) end @@ -35,7 +35,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController end def process_authorization_consent(approvedString) - endpoint = OpenidConnect::Authorization::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString)) + endpoint = OpenidConnect::Endpoints::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString)) restore_request_parameters(endpoint) handle_confirmation_endpoint_response(endpoint) end @@ -56,7 +56,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController req.update_param("redirect_uri", session[:redirect_uri]) req.update_param("response_type", session[:response_type]) endpoint.scopes, endpoint.request_object = - session[:scopes].map {|scope| Scope.find_by_name(scope) }, session[:request_object] + session[:scopes].map {|scope| OpenidConnect::Scope.find_by_name(scope) }, session[:request_object] end def to_boolean(str) diff --git a/app/controllers/openid_connect/clients_controller.rb b/app/controllers/openid_connect/clients_controller.rb index bc28752e3..a00b9d3d8 100644 --- a/app/controllers/openid_connect/clients_controller.rb +++ b/app/controllers/openid_connect/clients_controller.rb @@ -9,7 +9,7 @@ class OpenidConnect::ClientsController < ApplicationController def create registrar = OpenIDConnect::Client::Registrar.new(request.url, params) - client = OAuthApplication.register! registrar + client = OpenidConnect::OAuthApplication.register! registrar render json: client end diff --git a/app/controllers/openid_connect/discovery_controller.rb b/app/controllers/openid_connect/discovery_controller.rb index 020c05c2f..c06678fa3 100644 --- a/app/controllers/openid_connect/discovery_controller.rb +++ b/app/controllers/openid_connect/discovery_controller.rb @@ -1,32 +1,30 @@ -module OpenidConnect - class DiscoveryController < ApplicationController - def webfinger - jrd = { - links: [{ - rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, - href: File.join(root_url, "openid_connect") - }] - } - jrd[:subject] = params[:resource] if params[:resource].present? - render json: jrd, content_type: "application/jrd+json" - end +class OpenidConnect::DiscoveryController < ApplicationController + def webfinger + jrd = { + links: [{ + rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, + href: File.join(root_url, "openid_connect") + }] + } + jrd[:subject] = params[:resource] if params[:resource].present? + render json: jrd, content_type: "application/jrd+json" + end - def configuration - render json: OpenIDConnect::Discovery::Provider::Config::Response.new( - issuer: root_url, - registration_endpoint: openid_connect_clients_url, - authorization_endpoint: new_openid_connect_authorization_url, - token_endpoint: openid_connect_access_tokens_url, - userinfo_endpoint: api_v0_user_url, - jwks_uri: "https://not_configured_yet.com", # TODO: File.join({new_openid_connect_authorization_path} + "/jwks.json"), - scopes_supported: Scope.pluck(:name), - response_types_supported: OAuthApplication.available_response_types, - request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), - subject_types_supported: %w(public pairwise), - id_token_signing_alg_values_supported: %i(RS256), - token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), - # TODO: claims_supported: ["sub", "iss", "name", "email"] - ) - end + def configuration + render json: OpenIDConnect::Discovery::Provider::Config::Response.new( + issuer: root_url, + registration_endpoint: openid_connect_clients_url, + authorization_endpoint: new_openid_connect_authorization_url, + token_endpoint: openid_connect_access_tokens_url, + userinfo_endpoint: api_v0_user_url, + jwks_uri: "https://not_configured_yet.com", # TODO: File.join({new_openid_connect_authorization_path} + "/jwks.json"), + scopes_supported: Scope.pluck(:name), + response_types_supported: OAuthApplication.available_response_types, + request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), + subject_types_supported: %w(public pairwise), + id_token_signing_alg_values_supported: %i(RS256), + token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), + # TODO: claims_supported: ["sub", "iss", "name", "email"] + ) end end diff --git a/app/models/authorization.rb b/app/models/authorization.rb deleted file mode 100644 index a94e9edf2..000000000 --- a/app/models/authorization.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Authorization < ActiveRecord::Base - belongs_to :user - belongs_to :o_auth_application - has_many :scopes, through: :authorization_scopes - - # TODO: Incomplete class -end diff --git a/app/models/openid_connect/authorization.rb b/app/models/openid_connect/authorization.rb new file mode 100644 index 000000000..1b0b97f76 --- /dev/null +++ b/app/models/openid_connect/authorization.rb @@ -0,0 +1,43 @@ +class OpenidConnect::Authorization < ActiveRecord::Base + belongs_to :user + belongs_to :o_auth_application + has_many :scopes, through: :authorization_scopes + has_many :o_auth_access_tokens + + before_validation :setup, on: :create + + validates :refresh_token, uniqueness: true + validates :user, :o_auth_application, uniqueness: true + + # TODO: Incomplete class + + def setup + self.refresh_token = nil + end + + def self.valid?(token) + OpenidConnect::Authorization.exists? refresh_token: token + end + + def create_refresh_token + self.refresh_token = SecureRandom.hex(32) + end + + def create_token + o_auth_access_tokens.create!.bearer_token + end + + def self.find_by_client_id_and_user(client_id, user) + app = OpenidConnect::OAuthApplication.find_by(client_id: client_id) + find_by(o_auth_application: app, user: user) + end + + def self.find_or_create(client_id, user) + auth = find_by_client_id_and_user client_id, user + unless auth + # TODO: Handle creation error + auth = create! user: user, o_auth_application: OpenidConnect::OAuthApplication.find_by(client_id: client_id) + end + auth + end +end diff --git a/app/models/authorization_scope.rb b/app/models/openid_connect/authorization_scope.rb similarity index 100% rename from app/models/authorization_scope.rb rename to app/models/openid_connect/authorization_scope.rb diff --git a/app/models/token.rb b/app/models/openid_connect/o_auth_access_token.rb similarity index 84% rename from app/models/token.rb rename to app/models/openid_connect/o_auth_access_token.rb index e50796a38..bb7c8d90e 100644 --- a/app/models/token.rb +++ b/app/models/openid_connect/o_auth_access_token.rb @@ -1,7 +1,7 @@ -class Token < ActiveRecord::Base +class OpenidConnect::OAuthAccessToken < ActiveRecord::Base belongs_to :user + belongs_to :authorization has_many :scopes, through: :scope_tokens - has_one :refresh_token before_validation :setup, on: :create @@ -11,7 +11,6 @@ class Token < ActiveRecord::Base def setup self.token = SecureRandom.hex(32) - self.refresh_token = RefreshToken.create! self.expires_at = 24.hours.from_now end diff --git a/app/models/o_auth_application.rb b/app/models/openid_connect/o_auth_application.rb similarity index 85% rename from app/models/o_auth_application.rb rename to app/models/openid_connect/o_auth_application.rb index 0b6b2b9df..a5b1de35e 100644 --- a/app/models/o_auth_application.rb +++ b/app/models/openid_connect/o_auth_application.rb @@ -1,7 +1,8 @@ -class OAuthApplication < ActiveRecord::Base +class OpenidConnect::OAuthApplication < ActiveRecord::Base belongs_to :user has_many :authorizations + has_many :user, through: :authorizations validates :client_id, presence: true, uniqueness: true validates :client_secret, presence: true @@ -9,6 +10,7 @@ class OAuthApplication < ActiveRecord::Base serialize :redirect_uris, JSON before_validation :setup, on: :create + def setup self.client_id = SecureRandom.hex(16) self.client_secret = SecureRandom.hex(32) diff --git a/app/models/scope.rb b/app/models/openid_connect/scope.rb similarity index 79% rename from app/models/scope.rb rename to app/models/openid_connect/scope.rb index a2cccb2da..8fb8b28ed 100644 --- a/app/models/scope.rb +++ b/app/models/openid_connect/scope.rb @@ -1,4 +1,4 @@ -class Scope < ActiveRecord::Base +class OpenidConnect::Scope < ActiveRecord::Base has_many :tokens, through: :scope_tokens has_many :authorizations, through: :authorization_scopes diff --git a/app/models/scopes_tokens.rb b/app/models/openid_connect/scopes_tokens.rb similarity index 68% rename from app/models/scopes_tokens.rb rename to app/models/openid_connect/scopes_tokens.rb index 82f3d1c5e..de790ef63 100644 --- a/app/models/scopes_tokens.rb +++ b/app/models/openid_connect/scopes_tokens.rb @@ -1,4 +1,4 @@ -class ScopeToken < ActiveRecord::Base +class OpenidConnect::ScopeToken < ActiveRecord::Base belongs_to :scope belongs_to :token diff --git a/app/models/refresh_token.rb b/app/models/refresh_token.rb deleted file mode 100644 index 08fcc6f47..000000000 --- a/app/models/refresh_token.rb +++ /dev/null @@ -1,26 +0,0 @@ -class RefreshToken < ActiveRecord::Base - belongs_to :token - - before_validation :setup, on: :create - - validates :refresh_token, presence: true, uniqueness: true - - attr_reader :refresh_token - - def setup - self.refresh_token = SecureRandom.hex(32) - # No expipration date for now - end - - # Finds the requested refresh token and destroys it if found; returns true if found, false otherwise - def valid?(token) - the_token = RefreshToken.find_by_refresh_token token - if the_token - RefreshToken.destroy_all refresh_token: the_token.refresh_token - Token.destroy_all refresh_token: the_token.refresh_token - true - else - false - end - end -end diff --git a/app/models/user.rb b/app/models/user.rb index 6c98eeb73..945ae434e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -76,9 +76,9 @@ class User < ActiveRecord::Base has_many :reports - has_many :o_auth_applications - has_many :authorizations - has_many :tokens + has_many :authorizations, class_name: 'OpenidConnect::Authorization' + has_many :o_auth_applications, through: :authorizations, class_name: 'OpenidConnect::OAuthApplication' + has_many :o_auth_access_tokens, through: :authorizations, class_name: 'OpenidConnect::OAuthAccessToken' before_save :guard_unconfirmed_email, :save_person! @@ -602,15 +602,17 @@ class User < ActiveRecord::Base end end + def find_authorization_by_client_id(client_id) + OpenidConnect::Authorization.find_by_client_id_and_user client_id, self + end + private def clearable_fields - self.attributes.keys - ["id", "username", "encrypted_password", - "created_at", "updated_at", "locked_at", - "serialized_private_key", "getting_started", - "disable_mail", "show_community_spotlight_in_stream", - "strip_exif", "email", "remove_after", - "export", "exporting", "exported_at", - "exported_photos_file", "exporting_photos", "exported_photos_at"] + self.attributes.keys - %w(id username encrypted_password created_at updated_at locked_at + serialized_private_key getting_started + disable_mail show_community_spotlight_in_stream + strip_exif email remove_after export exporting exported_at + exported_photos_file exporting_photos exported_photos_at) end end diff --git a/config/application.rb b/config/application.rb index 49ddc6b1b..7fc97f5ca 100644 --- a/config/application.rb +++ b/config/application.rb @@ -109,7 +109,7 @@ module Diaspora config.action_mailer.asset_host = AppConfig.pod_uri.to_s config.middleware.use Rack::OAuth2::Server::Resource::Bearer, "OpenID Connect" do |req| - Token.valid(Time.now.utc).find_by(token: req.access_token) || req.invalid_token! + OpenidConnect::OAuthAccessToken.valid(Time.now.utc).find_by(token: req.access_token) || req.invalid_token! end end end diff --git a/db/migrate/20150614134031_create_o_auth_access_tokens.rb b/db/migrate/20150614134031_create_o_auth_access_tokens.rb new file mode 100644 index 000000000..8607500d1 --- /dev/null +++ b/db/migrate/20150614134031_create_o_auth_access_tokens.rb @@ -0,0 +1,13 @@ +class CreateOAuthAccessTokens < ActiveRecord::Migration + def self.up + create_table :o_auth_access_tokens do |t| + t.belongs_to :user, index: true + t.belongs_to :authorizations + t.belongs_to :endpoints + t.string :token + t.datetime :expires_at + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20150614134031_create_tokens.rb b/db/migrate/20150614134031_create_tokens.rb deleted file mode 100644 index 5a5e292f4..000000000 --- a/db/migrate/20150614134031_create_tokens.rb +++ /dev/null @@ -1,15 +0,0 @@ -class CreateTokens < ActiveRecord::Migration - def self.up - create_table :tokens do |t| - t.belongs_to :user, index: true - t.string :token - t.datetime :expires_at - - t.timestamps null: false - end - end - - def self.down - drop_table :tokens - end -end diff --git a/db/migrate/20150708153926_create_authorizations.rb b/db/migrate/20150708153926_create_authorizations.rb index 572bdc83c..24acdc081 100644 --- a/db/migrate/20150708153926_create_authorizations.rb +++ b/db/migrate/20150708153926_create_authorizations.rb @@ -3,6 +3,7 @@ class CreateAuthorizations < ActiveRecord::Migration create_table :authorizations do |t| t.belongs_to :user, index: true t.belongs_to :o_auth_application, index: true + t.string :refresh_token t.timestamps null: false end diff --git a/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb b/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb index c91e50d11..48a5929ca 100644 --- a/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb +++ b/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb @@ -1,7 +1,7 @@ class CreateAuthorizationsScopesJoinTable < ActiveRecord::Migration def change create_table :authorizations_scopes, id: false do |t| - t.belongs_to :authorization, index: true + t.belongs_to :endpoints, index: true t.belongs_to :scope, index: true end end diff --git a/db/migrate/20150708155747_create_scopes_tokens_join_table.rb b/db/migrate/20150708155747_create_scopes_tokens_join_table.rb index b0416e5e8..a52835048 100644 --- a/db/migrate/20150708155747_create_scopes_tokens_join_table.rb +++ b/db/migrate/20150708155747_create_scopes_tokens_join_table.rb @@ -2,7 +2,7 @@ class CreateScopesTokensJoinTable < ActiveRecord::Migration def change create_table :scopes_tokens, id: false do |t| t.belongs_to :scope, index: true - t.belongs_to :token, index: true + t.belongs_to :o_auth_access_token, index: true end end end diff --git a/db/migrate/20150713132035_create_refresh_token.rb b/db/migrate/20150713132035_create_refresh_token.rb deleted file mode 100644 index e03632e12..000000000 --- a/db/migrate/20150713132035_create_refresh_token.rb +++ /dev/null @@ -1,14 +0,0 @@ -class RefreshToken < ActiveRecord::Migration - def change - create_table :refresh_token do - t.belongs_to :token - t.string :refresh_token - - t.timestamps null: false - end - end - - def self.down - drop_table :refresh_token - end -end diff --git a/db/schema.rb b/db/schema.rb index 27ab1e24c..d5b98ce39 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151003142048) do +ActiveRecord::Schema.define(version: 20150708155747) do create_table "account_deletions", force: :cascade do |t| t.string "diaspora_handle", limit: 255 @@ -55,6 +55,25 @@ ActiveRecord::Schema.define(version: 20151003142048) do add_index "aspects", ["user_id", "contacts_visible"], name: "index_aspects_on_user_id_and_contacts_visible", using: :btree add_index "aspects", ["user_id"], name: "index_aspects_on_user_id", using: :btree + create_table "authorizations", force: :cascade do |t| + t.integer "user_id", limit: 4 + t.integer "o_auth_application_id", limit: 4 + t.string "refresh_token", limit: 255 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "authorizations", ["o_auth_application_id"], name: "index_authorizations_on_o_auth_application_id", using: :btree + add_index "authorizations", ["user_id"], name: "index_authorizations_on_user_id", using: :btree + + create_table "authorizations_scopes", id: false, force: :cascade do |t| + t.integer "authorization_id", limit: 4 + t.integer "scope_id", limit: 4 + end + + add_index "authorizations_scopes", ["authorization_id"], name: "index_authorizations_scopes_on_authorization_id", using: :btree + add_index "authorizations_scopes", ["scope_id"], name: "index_authorizations_scopes_on_scope_id", using: :btree + create_table "blocks", force: :cascade do |t| t.integer "user_id", limit: 4 t.integer "person_id", limit: 4 @@ -236,6 +255,17 @@ ActiveRecord::Schema.define(version: 20151003142048) do add_index "notifications", ["target_id"], name: "index_notifications_on_target_id", using: :btree add_index "notifications", ["target_type", "target_id"], name: "index_notifications_on_target_type_and_target_id", length: {"target_type"=>190, "target_id"=>nil}, using: :btree + create_table "o_auth_access_tokens", force: :cascade do |t| + t.integer "user_id", limit: 4 + t.integer "authorization_id", limit: 4 + t.string "token", limit: 255 + t.datetime "expires_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "o_auth_access_tokens", ["user_id"], name: "index_o_auth_access_tokens_on_user_id", using: :btree + create_table "o_auth_applications", force: :cascade do |t| t.integer "user_id", limit: 4 t.string "client_id", limit: 255 @@ -470,6 +500,20 @@ ActiveRecord::Schema.define(version: 20151003142048) do t.datetime "updated_at", null: false end + create_table "scopes", force: :cascade do |t| + t.string "name", limit: 255 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "scopes_tokens", id: false, force: :cascade do |t| + t.integer "scope_id", limit: 4 + t.integer "o_auth_access_token_id", limit: 4 + end + + add_index "scopes_tokens", ["o_auth_access_token_id"], name: "index_scopes_tokens_on_o_auth_access_token_id", using: :btree + add_index "scopes_tokens", ["scope_id"], name: "index_scopes_tokens_on_scope_id", using: :btree + create_table "services", force: :cascade do |t| t.string "type", limit: 127, null: false t.integer "user_id", limit: 4, null: false @@ -540,16 +584,6 @@ ActiveRecord::Schema.define(version: 20151003142048) do add_index "tags", ["name"], name: "index_tags_on_name", unique: true, length: {"name"=>191}, using: :btree - create_table "tokens", force: :cascade do |t| - t.integer "user_id", limit: 4 - t.string "token", limit: 255 - t.datetime "expires_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - add_index "tokens", ["user_id"], name: "index_tokens_on_user_id", using: :btree - create_table "user_preferences", force: :cascade do |t| t.string "email_type", limit: 255 t.integer "user_id", limit: 4 diff --git a/lib/account_deleter.rb b/lib/account_deleter.rb index 6018657cf..63d17fae5 100644 --- a/lib/account_deleter.rb +++ b/lib/account_deleter.rb @@ -47,15 +47,16 @@ class AccountDeleter #user deletions def normal_ar_user_associates_to_delete %i(tag_followings invitations_to_me services aspects user_preferences - notifications blocks authorizations o_auth_applications tokens) + notifications blocks authorizations o_auth_applications o_auth_access_tokens) end def special_ar_user_associations - [:invitations_from_me, :person, :profile, :contacts, :auto_follow_back_aspect] + %i(invitations_from_me person profile contacts auto_follow_back_aspect) end def ignored_ar_user_associations - [:followed_tags, :invited_by, :contact_people, :aspect_memberships, :ignored_people, :conversation_visibilities, :conversations, :reports] + %i(followed_tags invited_by contact_people aspect_memberships + ignored_people conversation_visibilities conversations reports) end def delete_standard_user_associations diff --git a/lib/openid_connect/authorization/endpoint.rb b/lib/openid_connect/endpoints/endpoint.rb similarity index 77% rename from lib/openid_connect/authorization/endpoint.rb rename to lib/openid_connect/endpoints/endpoint.rb index 0d1adc5b1..8bd813102 100644 --- a/lib/openid_connect/authorization/endpoint.rb +++ b/lib/openid_connect/endpoints/endpoint.rb @@ -1,5 +1,5 @@ module OpenidConnect - module Authorization + module Endpoints class Endpoint attr_accessor :app, :user, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object @@ -9,7 +9,8 @@ module OpenidConnect @user = current_user @app = Rack::OAuth2::Server::Authorize.new do |req, res| build_attributes(req, res) - if OAuthApplication.available_response_types.include? Array(req.response_type).map(&:to_s).join(" ") + if OpenidConnect::OAuthApplication.available_response_types.include?( + Array(req.response_type).map(&:to_s).join(" ")) handle_response_type(req, res) else req.unsupported_response_type! @@ -29,7 +30,7 @@ module OpenidConnect private def build_client(req) - @client = OAuthApplication.find_by_client_id(req.client_id) || req.bad_request! + @client = OpenidConnect::OAuthApplication.find_by_client_id(req.client_id) || req.bad_request! end def build_redirect_uri(req, res) diff --git a/lib/openid_connect/authorization/endpoint_confirmation_point.rb b/lib/openid_connect/endpoints/endpoint_confirmation_point.rb similarity index 97% rename from lib/openid_connect/authorization/endpoint_confirmation_point.rb rename to lib/openid_connect/endpoints/endpoint_confirmation_point.rb index 1e8a61423..fcb666aec 100644 --- a/lib/openid_connect/authorization/endpoint_confirmation_point.rb +++ b/lib/openid_connect/endpoints/endpoint_confirmation_point.rb @@ -1,5 +1,5 @@ module OpenidConnect - module Authorization + module Endpoints class EndpointConfirmationPoint < Endpoint def initialize(current_user, approved=false) super(current_user) diff --git a/lib/openid_connect/authorization/endpoint_start_point.rb b/lib/openid_connect/endpoints/endpoint_start_point.rb similarity index 89% rename from lib/openid_connect/authorization/endpoint_start_point.rb rename to lib/openid_connect/endpoints/endpoint_start_point.rb index 5d44e7dc5..51f1786c4 100644 --- a/lib/openid_connect/authorization/endpoint_start_point.rb +++ b/lib/openid_connect/endpoints/endpoint_start_point.rb @@ -1,5 +1,5 @@ module OpenidConnect - module Authorization + module Endpoints class EndpointStartPoint < Endpoint def initialize(current_user) super(current_user) @@ -24,7 +24,7 @@ module OpenidConnect def build_scopes(req) @scopes = req.scope.map {|scope| - Scope.where(name: scope).first.tap do |scope| + OpenidConnect::Scope.where(name: scope).first.tap do |scope| req.invalid_scope! "Unknown scope: #{scope}" unless scope end } diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb index 197d9021c..4714ad481 100644 --- a/lib/openid_connect/token_endpoint.rb +++ b/lib/openid_connect/token_endpoint.rb @@ -29,7 +29,8 @@ module OpenidConnect user = User.find_for_database_authentication(username: req.username) if user if user.valid_password?(req.password) - res.access_token = token! user + auth = OpenidConnect::Authorization.find_or_create(req.client_id, user) + res.access_token = auth.create_token else req.invalid_grant! end @@ -39,24 +40,21 @@ module OpenidConnect end def handle_refresh_flow(req, res) - user = OAuthApplication.find_by_client_id(req.client_id).user - if RefreshToken.valid?(req.refresh_token) - res.access_token = token! user + auth = OpenidConnect::Authorization.find_by_client_id req.client_id + if OpenidConnect::Authorization.valid? req.refresh_token + res.access_token = auth.create_token else req.invalid_grant! end end def retrieve_client(req) - OAuthApplication.find_by_client_id req.client_id + OpenidConnect::OAuthApplication.find_by client_id: req.client_id end def app_valid?(o_auth_app, req) o_auth_app.client_secret == req.client_secret end - def token!(user) - user.tokens.create!.bearer_token - end end end diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb index 974e0620b..6bfe7d3da 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -1,16 +1,16 @@ require "spec_helper" describe OpenidConnect::AuthorizationsController, type: :controller do - let!(:client) { OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) } + let!(:client) { OpenidConnect::OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) } let!(:client_with_multiple_redirects) do - OAuthApplication.create!( + OpenidConnect::OAuthApplication.create!( name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/", "http://localhost/"]) end before do sign_in :user, alice allow(@controller).to receive(:current_user).and_return(alice) - Scope.create!(name: "openid") + OpenidConnect::Scope.create!(name: "openid") end describe "#new" do diff --git a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb index 247df0661..11df9e303 100644 --- a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb @@ -2,7 +2,11 @@ require "spec_helper" describe OpenidConnect::ProtectedResourceEndpoint, type: :request do describe "getting the user info" do - let!(:token) { bob.tokens.create!.bearer_token.to_s } + let!(:client) do + OpenidConnect::OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) + end + let(:auth) { OpenidConnect::Authorization.find_or_create(client.client_id, bob) } + let!(:token) { auth.create_token.to_s } let(:invalid_token) { SecureRandom.hex(32).to_s } # TODO: Add tests for expired access tokens diff --git a/spec/lib/openid_connect/token_endpoint_spec.rb b/spec/lib/openid_connect/token_endpoint_spec.rb index b0d36aafe..1b60704b5 100644 --- a/spec/lib/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/openid_connect/token_endpoint_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe OpenidConnect::TokenEndpoint, type: :request do - let!(:client) { OAuthApplication.create!(redirect_uris: ["http://localhost"]) } + let!(:client) { OpenidConnect::OAuthApplication.create!(redirect_uris: ["http://localhost"]) } describe "the password grant type" do context "when the username field is missing" do it "should return an invalid request error" do From 1475672d720494bbcfea9f45f77ad093e14cd595 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 15 Jul 2015 16:16:50 +0900 Subject: [PATCH 019/105] Fix authorization and related models Squashed commits: [a844d37] Remove unnecessary class_name's from models [529a30c] Further adjust authorization and related models --- app/controllers/api/v0/base_controller.rb | 4 ++ app/controllers/api/v0/users_controller.rb | 2 +- app/models/openid_connect/authorization.rb | 39 ++++++++----------- .../openid_connect/authorization_scope.rb | 10 ++--- .../openid_connect/o_auth_access_token.rb | 4 +- .../openid_connect/o_auth_application.rb | 2 - app/models/openid_connect/scope.rb | 2 +- app/models/openid_connect/scopes_tokens.rb | 2 +- app/models/user.rb | 6 +-- ...50614134031_create_o_auth_access_tokens.rb | 4 +- ...create_authorizations_scopes_join_table.rb | 2 +- db/schema.rb | 3 +- lib/openid_connect/token_endpoint.rb | 8 ++-- .../protected_resource_endpoint_spec.rb | 10 ++--- 14 files changed, 46 insertions(+), 52 deletions(-) diff --git a/app/controllers/api/v0/base_controller.rb b/app/controllers/api/v0/base_controller.rb index f95c9c5c7..982800edd 100644 --- a/app/controllers/api/v0/base_controller.rb +++ b/app/controllers/api/v0/base_controller.rb @@ -2,4 +2,8 @@ class Api::V0::BaseController < ApplicationController include OpenidConnect::ProtectedResourceEndpoint before_filter :require_access_token + + def authorization + current_token.authorization + end end diff --git a/app/controllers/api/v0/users_controller.rb b/app/controllers/api/v0/users_controller.rb index c09f4cdb4..57b2c300c 100644 --- a/app/controllers/api/v0/users_controller.rb +++ b/app/controllers/api/v0/users_controller.rb @@ -6,6 +6,6 @@ class Api::V0::UsersController < Api::V0::BaseController private def user - current_token.user + authorization.user end end diff --git a/app/models/openid_connect/authorization.rb b/app/models/openid_connect/authorization.rb index 1b0b97f76..a13a861bd 100644 --- a/app/models/openid_connect/authorization.rb +++ b/app/models/openid_connect/authorization.rb @@ -1,43 +1,38 @@ class OpenidConnect::Authorization < ActiveRecord::Base belongs_to :user belongs_to :o_auth_application + has_many :scopes, through: :authorization_scopes has_many :o_auth_access_tokens before_validation :setup, on: :create - validates :refresh_token, uniqueness: true - validates :user, :o_auth_application, uniqueness: true - - # TODO: Incomplete class + validates :refresh_token, presence: true, uniqueness: true + validates :user, presence: true, uniqueness: true + validates :o_auth_application, presence: true, uniqueness: true def setup - self.refresh_token = nil - end - - def self.valid?(token) - OpenidConnect::Authorization.exists? refresh_token: token - end - - def create_refresh_token self.refresh_token = SecureRandom.hex(32) end - def create_token - o_auth_access_tokens.create!.bearer_token + def create_access_token + OpenidConnect::OAuthAccessToken.create!(authorization: self).bearer_token end - def self.find_by_client_id_and_user(client_id, user) - app = OpenidConnect::OAuthApplication.find_by(client_id: client_id) + # TODO: Actually call this method from token endpoint + def regenerate_refresh_token + self.refresh_token = SecureRandom.hex(32) + end + + def self.find_by_client_id_and_user(app, user) find_by(o_auth_application: app, user: user) end + # TODO: Handle creation error def self.find_or_create(client_id, user) - auth = find_by_client_id_and_user client_id, user - unless auth - # TODO: Handle creation error - auth = create! user: user, o_auth_application: OpenidConnect::OAuthApplication.find_by(client_id: client_id) - end - auth + app = OpenidConnect::OAuthApplication.find_by(client_id: client_id) + find_by_client_id_and_user(app, user) || create!(user: user, o_auth_application: app) end + + # TODO: Incomplete class end diff --git a/app/models/openid_connect/authorization_scope.rb b/app/models/openid_connect/authorization_scope.rb index d8a88d779..30e160281 100644 --- a/app/models/openid_connect/authorization_scope.rb +++ b/app/models/openid_connect/authorization_scope.rb @@ -1,7 +1,7 @@ -class AuthorizationScope < ActiveRecord::Base - belongs_to authorization - belongs_to scope +class OpenidConnect::AuthorizationScope < ActiveRecord::Base + belongs_to :authorization + belongs_to :scope - validates authorization, presence: true - validates scope, presence: true + validates :authorization, presence: true + validates :scope, presence: true end diff --git a/app/models/openid_connect/o_auth_access_token.rb b/app/models/openid_connect/o_auth_access_token.rb index bb7c8d90e..707bdc223 100644 --- a/app/models/openid_connect/o_auth_access_token.rb +++ b/app/models/openid_connect/o_auth_access_token.rb @@ -1,11 +1,11 @@ class OpenidConnect::OAuthAccessToken < ActiveRecord::Base - belongs_to :user - belongs_to :authorization + belongs_to :authorization, dependent: :delete has_many :scopes, through: :scope_tokens before_validation :setup, on: :create validates :token, presence: true, uniqueness: true + validates :authorization, presence: true, uniqueness: true scope :valid, ->(time) { where("expires_at >= ?", time) } diff --git a/app/models/openid_connect/o_auth_application.rb b/app/models/openid_connect/o_auth_application.rb index a5b1de35e..2ba85ba0b 100644 --- a/app/models/openid_connect/o_auth_application.rb +++ b/app/models/openid_connect/o_auth_application.rb @@ -1,6 +1,4 @@ class OpenidConnect::OAuthApplication < ActiveRecord::Base - belongs_to :user - has_many :authorizations has_many :user, through: :authorizations diff --git a/app/models/openid_connect/scope.rb b/app/models/openid_connect/scope.rb index 8fb8b28ed..e5449b648 100644 --- a/app/models/openid_connect/scope.rb +++ b/app/models/openid_connect/scope.rb @@ -1,5 +1,5 @@ class OpenidConnect::Scope < ActiveRecord::Base - has_many :tokens, through: :scope_tokens + has_many :o_auth_access_token, through: :scope_tokens has_many :authorizations, through: :authorization_scopes validates :name, presence: true, uniqueness: true diff --git a/app/models/openid_connect/scopes_tokens.rb b/app/models/openid_connect/scopes_tokens.rb index de790ef63..3c336ac73 100644 --- a/app/models/openid_connect/scopes_tokens.rb +++ b/app/models/openid_connect/scopes_tokens.rb @@ -1,6 +1,6 @@ class OpenidConnect::ScopeToken < ActiveRecord::Base belongs_to :scope - belongs_to :token + belongs_to :o_auth_access_token validates :scope, presence: true validates :token, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index 945ae434e..28fa845a2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -76,9 +76,9 @@ class User < ActiveRecord::Base has_many :reports - has_many :authorizations, class_name: 'OpenidConnect::Authorization' - has_many :o_auth_applications, through: :authorizations, class_name: 'OpenidConnect::OAuthApplication' - has_many :o_auth_access_tokens, through: :authorizations, class_name: 'OpenidConnect::OAuthAccessToken' + has_many :authorizations, class_name: "OpenidConnect::Authorization" + has_many :o_auth_applications, through: :authorizations, class_name: "OpenidConnect::OAuthApplication" + has_many :o_auth_access_tokens, through: :authorizations, class_name: "OpenidConnect::OAuthAccessToken" before_save :guard_unconfirmed_email, :save_person! diff --git a/db/migrate/20150614134031_create_o_auth_access_tokens.rb b/db/migrate/20150614134031_create_o_auth_access_tokens.rb index 8607500d1..5084d61e5 100644 --- a/db/migrate/20150614134031_create_o_auth_access_tokens.rb +++ b/db/migrate/20150614134031_create_o_auth_access_tokens.rb @@ -1,9 +1,7 @@ class CreateOAuthAccessTokens < ActiveRecord::Migration def self.up create_table :o_auth_access_tokens do |t| - t.belongs_to :user, index: true - t.belongs_to :authorizations - t.belongs_to :endpoints + t.belongs_to :authorization, index: true t.string :token t.datetime :expires_at diff --git a/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb b/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb index 48a5929ca..c91e50d11 100644 --- a/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb +++ b/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb @@ -1,7 +1,7 @@ class CreateAuthorizationsScopesJoinTable < ActiveRecord::Migration def change create_table :authorizations_scopes, id: false do |t| - t.belongs_to :endpoints, index: true + t.belongs_to :authorization, index: true t.belongs_to :scope, index: true end end diff --git a/db/schema.rb b/db/schema.rb index d5b98ce39..7b3993f06 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -256,7 +256,6 @@ ActiveRecord::Schema.define(version: 20150708155747) do add_index "notifications", ["target_type", "target_id"], name: "index_notifications_on_target_type_and_target_id", length: {"target_type"=>190, "target_id"=>nil}, using: :btree create_table "o_auth_access_tokens", force: :cascade do |t| - t.integer "user_id", limit: 4 t.integer "authorization_id", limit: 4 t.string "token", limit: 255 t.datetime "expires_at" @@ -264,7 +263,7 @@ ActiveRecord::Schema.define(version: 20150708155747) do t.datetime "updated_at", null: false end - add_index "o_auth_access_tokens", ["user_id"], name: "index_o_auth_access_tokens_on_user_id", using: :btree + add_index "o_auth_access_tokens", ["authorization_id"], name: "index_o_auth_access_tokens_on_authorization_id", using: :btree create_table "o_auth_applications", force: :cascade do |t| t.integer "user_id", limit: 4 diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb index 4714ad481..14dcfa94c 100644 --- a/lib/openid_connect/token_endpoint.rb +++ b/lib/openid_connect/token_endpoint.rb @@ -30,7 +30,7 @@ module OpenidConnect if user if user.valid_password?(req.password) auth = OpenidConnect::Authorization.find_or_create(req.client_id, user) - res.access_token = auth.create_token + res.access_token = auth.create_access_token else req.invalid_grant! end @@ -40,9 +40,9 @@ module OpenidConnect end def handle_refresh_flow(req, res) - auth = OpenidConnect::Authorization.find_by_client_id req.client_id - if OpenidConnect::Authorization.valid? req.refresh_token - res.access_token = auth.create_token + auth = OpenidConnect::Authorization.where(client_id: req.client_id).where(refresh_token: req.refresh_token).first + if auth + res.access_token = auth.create_access_token else req.invalid_grant! end diff --git a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb index 11df9e303..bb95fda69 100644 --- a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb @@ -5,20 +5,20 @@ describe OpenidConnect::ProtectedResourceEndpoint, type: :request do let!(:client) do OpenidConnect::OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) end - let(:auth) { OpenidConnect::Authorization.find_or_create(client.client_id, bob) } - let!(:token) { auth.create_token.to_s } - let(:invalid_token) { SecureRandom.hex(32).to_s } + let!(:auth) { OpenidConnect::Authorization.find_or_create(client.client_id, bob) } + let!(:access_token) { auth.create_access_token.to_s } + let!(:invalid_token) { SecureRandom.hex(32).to_s } # TODO: Add tests for expired access tokens context "when access token is valid" do it "shows the user's username and email" do - get "/api/v0/user/", access_token: token + get "/api/v0/user/", access_token: access_token json_body = JSON.parse(response.body) expect(json_body["username"]).to eq(bob.username) expect(json_body["email"]).to eq(bob.email) end it "should include private in the cache-control header" do - get "/api/v0/user/", access_token: token + get "/api/v0/user/", access_token: access_token expect(response.headers["Cache-Control"]).to include("private") end end From 17fde49d61c2cb8f77724f6c1b937e11101ac67b Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 15 Jul 2015 14:17:21 +0900 Subject: [PATCH 020/105] Implement ID Token for the implicit flow --- .../authorizations_controller.rb | 46 ++++++++++++------- .../openid_connect/discovery_controller.rb | 4 +- .../openid_connect/id_tokens_controller.rb | 5 ++ app/models/id_token.rb | 38 +++++++++++++++ app/models/user.rb | 1 + .../authorizations/new.html.haml | 2 +- config/routes.rb | 1 + db/migrate/20150714055110_create_id_tokens.rb | 12 +++++ db/schema.rb | 14 +++++- lib/account_deleter.rb | 2 +- lib/openid_connect/endpoints/endpoint.rb | 13 +++--- .../endpoints/endpoint_confirmation_point.rb | 5 +- .../endpoints/endpoint_start_point.rb | 4 -- lib/openid_connect/id_token_config.rb | 11 +++++ .../protected_resource_endpoint.rb | 10 +--- .../authorizations_controller_spec.rb | 6 ++- .../id_tokens_controller_spec.rb | 19 ++++++++ spec/requests/api/v2/base_controller_spec.rb | 4 -- 18 files changed, 150 insertions(+), 47 deletions(-) create mode 100644 app/controllers/openid_connect/id_tokens_controller.rb create mode 100644 app/models/id_token.rb create mode 100644 db/migrate/20150714055110_create_id_tokens.rb create mode 100644 lib/openid_connect/id_token_config.rb create mode 100644 spec/controllers/openid_connect/id_tokens_controller_spec.rb delete mode 100644 spec/requests/api/v2/base_controller_spec.rb diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index e5f39e8b0..46f226fb2 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -17,7 +17,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController private def request_authorization_consent_form - endpoint = OpenidConnect::Endpoints::EndpointStartPoint.new(current_user) + endpoint = OpenidConnect::Authorization::EndpointStartPoint.new(current_user) handle_startpoint_response(endpoint) end @@ -26,28 +26,28 @@ class OpenidConnect::AuthorizationsController < ApplicationController if response.redirect? redirect_to header["Location"] else - @client, @response_type, @redirect_uri, @scopes, @request_object = *[ - endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint.request_object - ] - save_request_parameters - render :new + saveParamsAndRenderConsentForm(endpoint) end end def process_authorization_consent(approvedString) - endpoint = OpenidConnect::Endpoints::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString)) - restore_request_parameters(endpoint) + endpoint = OpenidConnect::Authorization::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString)) handle_confirmation_endpoint_response(endpoint) end - def handle_confirmation_endpoint_response(endpoint) - _status, header, _response = *endpoint.call(request.env) - redirect_to header["Location"] + def saveParamsAndRenderConsentForm(endpoint) + @o_auth_application, @response_type, @redirect_uri, @scopes, @request_object = *[ + endpoint.o_auth_application, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint.request_object + ] + save_request_parameters + render :new end - def save_request_parameters - session[:client_id], session[:response_type], session[:redirect_uri], session[:scopes], session[:request_object] = - @client.client_id, @response_type, @redirect_uri, @scopes.map(&:name), @request_object + def handle_confirmation_endpoint_response(endpoint) + restore_request_parameters(endpoint) + _status, header, _response = *endpoint.call(request.env) + delete_authorization_session_variables + redirect_to header["Location"] end def restore_request_parameters(endpoint) @@ -55,8 +55,22 @@ class OpenidConnect::AuthorizationsController < ApplicationController req.update_param("client_id", session[:client_id]) req.update_param("redirect_uri", session[:redirect_uri]) req.update_param("response_type", session[:response_type]) - endpoint.scopes, endpoint.request_object = - session[:scopes].map {|scope| OpenidConnect::Scope.find_by_name(scope) }, session[:request_object] + endpoint.scopes, endpoint.request_object, endpoint.nonce = + session[:scopes].map {|scope| Scope.find_by_name(scope) }, session[:request_object], session[:nonce] + end + + def delete_authorization_session_variables + session.delete(:client_id) + session.delete(:response_type) + session.delete(:redirect_uri) + session.delete(:scopes) + session.delete(:request_object) + session.delete(:nonce) + end + + def save_request_parameters + session[:client_id], session[:response_type], session[:redirect_uri], session[:scopes], session[:request_object], session[:nonce] = + @o_auth_application.client_id, @response_type, @redirect_uri, @scopes.map(&:name), @request_object, params[:nonce] end def to_boolean(str) diff --git a/app/controllers/openid_connect/discovery_controller.rb b/app/controllers/openid_connect/discovery_controller.rb index c06678fa3..d2b6745f1 100644 --- a/app/controllers/openid_connect/discovery_controller.rb +++ b/app/controllers/openid_connect/discovery_controller.rb @@ -17,14 +17,14 @@ class OpenidConnect::DiscoveryController < ApplicationController authorization_endpoint: new_openid_connect_authorization_url, token_endpoint: openid_connect_access_tokens_url, userinfo_endpoint: api_v0_user_url, - jwks_uri: "https://not_configured_yet.com", # TODO: File.join({new_openid_connect_authorization_path} + "/jwks.json"), + jwks_uri: File.join(root_url, "openid_connect", "jwks.json"), scopes_supported: Scope.pluck(:name), response_types_supported: OAuthApplication.available_response_types, request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), subject_types_supported: %w(public pairwise), id_token_signing_alg_values_supported: %i(RS256), token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), - # TODO: claims_supported: ["sub", "iss", "name", "email"] + # TODO: claims_supported: ["sub", "iss", "name", "email"] ) end end diff --git a/app/controllers/openid_connect/id_tokens_controller.rb b/app/controllers/openid_connect/id_tokens_controller.rb new file mode 100644 index 000000000..75e0d69b4 --- /dev/null +++ b/app/controllers/openid_connect/id_tokens_controller.rb @@ -0,0 +1,5 @@ +class OpenidConnect::IdTokensController < ApplicationController + def jwks + render json: JSON::JWK::Set.new(JSON::JWK.new(OpenidConnect::IdTokenConfig.public_key, use: :sig)).as_json + end +end diff --git a/app/models/id_token.rb b/app/models/id_token.rb new file mode 100644 index 000000000..fc65b3751 --- /dev/null +++ b/app/models/id_token.rb @@ -0,0 +1,38 @@ +class IdToken < ActiveRecord::Base + belongs_to :user + belongs_to :o_auth_application + + before_validation :setup, on: :create + + validates :user, presence: true + validates :o_auth_application, presence: true + + default_scope -> { where("expires_at >= ?", Time.now.utc) } + + def setup + self.expires_at = 30.minutes.from_now + end + + def to_jwt(options = {}) + to_response_object(options).to_jwt OpenidConnect::IdTokenConfig.private_key + end + + def to_response_object(options = {}) + claims = { + iss: AppConfig.environment.url, + sub: AppConfig.environment.url + o_auth_application.client_id.to_s + user.id.to_s, # TODO: Convert to proper PPID + aud: o_auth_application.client_id, + exp: expires_at.to_i, + iat: created_at.to_i, + auth_time: user.current_sign_in_at.to_i, + nonce: nonce, + acr: 0 # TODO: Adjust ? + } + id_token = OpenIDConnect::ResponseObject::IdToken.new(claims) + id_token.code = options[:code] if options[:code] + id_token.access_token = options[:access_token] if options[:access_token] + id_token + end + + # TODO: Add support for request objects +end diff --git a/app/models/user.rb b/app/models/user.rb index 28fa845a2..88612b66d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -79,6 +79,7 @@ class User < ActiveRecord::Base has_many :authorizations, class_name: "OpenidConnect::Authorization" has_many :o_auth_applications, through: :authorizations, class_name: "OpenidConnect::OAuthApplication" has_many :o_auth_access_tokens, through: :authorizations, class_name: "OpenidConnect::OAuthAccessToken" + has_many :id_tokens, class_name: "OpenidConnect::IdToken" before_save :guard_unconfirmed_email, :save_person! diff --git a/app/views/openid_connect/authorizations/new.html.haml b/app/views/openid_connect/authorizations/new.html.haml index 31726c0fd..c134be8a7 100644 --- a/app/views/openid_connect/authorizations/new.html.haml +++ b/app/views/openid_connect/authorizations/new.html.haml @@ -1,4 +1,4 @@ -%h2= @client.name +%h2= @o_auth_application.name %p= t(".will_be_redirected") = @redirect_uri = t(".with_id_token") diff --git a/config/routes.rb b/config/routes.rb index 79021f7cd..e1c9bdf2e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -245,6 +245,7 @@ Diaspora::Application.routes.draw do get ".well-known/webfinger", to: "discovery#webfinger" get ".well-known/openid-configuration", to: "discovery#configuration" + get "jwks.json", to: "id_tokens#jwks" end api_version(module: "Api::V0", path: {value: "api/v0"}, default: true) do diff --git a/db/migrate/20150714055110_create_id_tokens.rb b/db/migrate/20150714055110_create_id_tokens.rb new file mode 100644 index 000000000..0aadbe880 --- /dev/null +++ b/db/migrate/20150714055110_create_id_tokens.rb @@ -0,0 +1,12 @@ +class CreateIdTokens < ActiveRecord::Migration + def change + create_table :id_tokens do |t| + t.belongs_to :user, index: true + t.belongs_to :o_auth_application, index: true + t.datetime :expires_at + t.string :nonce + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 7b3993f06..2a7e29092 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150708155747) do +ActiveRecord::Schema.define(version: 20150714055110) do create_table "account_deletions", force: :cascade do |t| t.string "diaspora_handle", limit: 255 @@ -156,6 +156,18 @@ ActiveRecord::Schema.define(version: 20150708155747) do add_index "conversations", ["author_id"], name: "conversations_author_id_fk", using: :btree + create_table "id_tokens", force: :cascade do |t| + t.integer "user_id", limit: 4 + t.integer "o_auth_application_id", limit: 4 + t.datetime "expires_at" + t.string "nonce", limit: 255 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "id_tokens", ["o_auth_application_id"], name: "index_id_tokens_on_o_auth_application_id", using: :btree + add_index "id_tokens", ["user_id"], name: "index_id_tokens_on_user_id", using: :btree + create_table "invitation_codes", force: :cascade do |t| t.string "token", limit: 255 t.integer "user_id", limit: 4 diff --git a/lib/account_deleter.rb b/lib/account_deleter.rb index 63d17fae5..c102cb6c0 100644 --- a/lib/account_deleter.rb +++ b/lib/account_deleter.rb @@ -47,7 +47,7 @@ class AccountDeleter #user deletions def normal_ar_user_associates_to_delete %i(tag_followings invitations_to_me services aspects user_preferences - notifications blocks authorizations o_auth_applications o_auth_access_tokens) + notifications blocks authorizations o_auth_applications o_auth_access_tokens id_tokens) end def special_ar_user_associations diff --git a/lib/openid_connect/endpoints/endpoint.rb b/lib/openid_connect/endpoints/endpoint.rb index 8bd813102..9a1b35eca 100644 --- a/lib/openid_connect/endpoints/endpoint.rb +++ b/lib/openid_connect/endpoints/endpoint.rb @@ -1,16 +1,15 @@ module OpenidConnect - module Endpoints + module Authorization class Endpoint - attr_accessor :app, :user, :client, :redirect_uri, :response_type, - :scopes, :_request_, :request_uri, :request_object + attr_accessor :app, :user, :o_auth_application, :redirect_uri, :response_type, + :scopes, :_request_, :request_uri, :request_object, :nonce delegate :call, to: :app def initialize(current_user) @user = current_user @app = Rack::OAuth2::Server::Authorize.new do |req, res| build_attributes(req, res) - if OpenidConnect::OAuthApplication.available_response_types.include?( - Array(req.response_type).map(&:to_s).join(" ")) + if OAuthApplication.available_response_types.include? Array(req.response_type).map(&:to_s).join(" ") handle_response_type(req, res) else req.unsupported_response_type! @@ -30,11 +29,11 @@ module OpenidConnect private def build_client(req) - @client = OpenidConnect::OAuthApplication.find_by_client_id(req.client_id) || req.bad_request! + @o_auth_application = OAuthApplication.find_by_client_id(req.client_id) || req.bad_request! end def build_redirect_uri(req, res) - res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@client.redirect_uris) + res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@o_auth_application.redirect_uris) end end end diff --git a/lib/openid_connect/endpoints/endpoint_confirmation_point.rb b/lib/openid_connect/endpoints/endpoint_confirmation_point.rb index fcb666aec..2400d6db1 100644 --- a/lib/openid_connect/endpoints/endpoint_confirmation_point.rb +++ b/lib/openid_connect/endpoints/endpoint_confirmation_point.rb @@ -26,7 +26,10 @@ module OpenidConnect def approved!(req, res) response_types = Array(req.response_type) if response_types.include?(:id_token) - res.id_token = SecureRandom.hex(16) # TODO: Replace with real ID token + id_token = @user.id_tokens.create!(o_auth_application: o_auth_application, nonce: @nonce) + options = %i(code access_token).map{|option| ["res.#{option}", res.respond_to?(option) ? res.option : nil]}.to_h + res.id_token = id_token.to_jwt(options) + # TODO: Add support for request object end res.approve! end diff --git a/lib/openid_connect/endpoints/endpoint_start_point.rb b/lib/openid_connect/endpoints/endpoint_start_point.rb index 51f1786c4..0a167578f 100644 --- a/lib/openid_connect/endpoints/endpoint_start_point.rb +++ b/lib/openid_connect/endpoints/endpoint_start_point.rb @@ -1,10 +1,6 @@ module OpenidConnect module Endpoints class EndpointStartPoint < Endpoint - def initialize(current_user) - super(current_user) - end - def handle_response_type(req, res) @response_type = req.response_type end diff --git a/lib/openid_connect/id_token_config.rb b/lib/openid_connect/id_token_config.rb new file mode 100644 index 000000000..cac535c03 --- /dev/null +++ b/lib/openid_connect/id_token_config.rb @@ -0,0 +1,11 @@ +module OpenidConnect + class IdTokenConfig + @@key = OpenSSL::PKey::RSA.new(2048) + def self.public_key + @@key.public_key + end + def self.private_key + @@key + end + end +end diff --git a/lib/openid_connect/protected_resource_endpoint.rb b/lib/openid_connect/protected_resource_endpoint.rb index 4b7a7ea9c..8d5a37165 100644 --- a/lib/openid_connect/protected_resource_endpoint.rb +++ b/lib/openid_connect/protected_resource_endpoint.rb @@ -1,14 +1,6 @@ module OpenidConnect module ProtectedResourceEndpoint - def self.included(klass) - klass.send :include, ProtectedResourceEndpoint::Helper - end - - module Helper - def current_token - @current_token - end - end + attr_reader :current_token def require_access_token @current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN] diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb index 6bfe7d3da..a8f9d4f60 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -91,7 +91,7 @@ describe OpenidConnect::AuthorizationsController, type: :controller do describe "#create" do before do get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", - scope: "openid", nonce: SecureRandom.hex(16), state: 418_093_098_3 + scope: "openid", nonce: 418_093_098_3, state: 418_093_098_3 end context "when authorization is approved" do @@ -101,6 +101,10 @@ describe OpenidConnect::AuthorizationsController, type: :controller do it "should return the id token in a fragment" do expect(response.location).to have_content("id_token=") + encoded_id_token = response.location[/(?<=id_token=)[^&]+/] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key + expect(decoded_token.nonce).to eq("4180930983") + expect(decoded_token.exp).to be > Time.now.utc.to_i end it "should return the passed in state" do diff --git a/spec/controllers/openid_connect/id_tokens_controller_spec.rb b/spec/controllers/openid_connect/id_tokens_controller_spec.rb new file mode 100644 index 000000000..6739a3561 --- /dev/null +++ b/spec/controllers/openid_connect/id_tokens_controller_spec.rb @@ -0,0 +1,19 @@ +require "spec_helper" + +describe OpenidConnect::IdTokensController, type: :controller do + describe "#jwks" do + before do + get :jwks + end + + it "should contain a public key that matches the internal private key" do + json = JSON.parse(response.body).with_indifferent_access + jwks = JSON::JWK::Set.new json[:keys] + public_keys = jwks.collect do |jwk| + JSON::JWK.decode jwk + end + public_key = public_keys.first + expect(OpenidConnect::IdTokenConfig.private_key.public_key.to_s).to eq(public_key.to_s) + end + end +end diff --git a/spec/requests/api/v2/base_controller_spec.rb b/spec/requests/api/v2/base_controller_spec.rb deleted file mode 100644 index dc359bbe2..000000000 --- a/spec/requests/api/v2/base_controller_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require "spec_helper" - -describe Api::V0::BaseController do -end From 2d762da072ba704304a52227d0dd8039ea0566fc Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 15 Jul 2015 23:35:48 +0900 Subject: [PATCH 021/105] Adjust tokens to fit revised Authorization --- app/controllers/api/v0/base_controller.rb | 3 - app/controllers/api/v0/users_controller.rb | 2 +- .../authorizations_controller.rb | 57 +++---- app/models/openid_connect/authorization.rb | 26 ++-- app/models/{ => openid_connect}/id_token.rb | 14 +- .../openid_connect/o_auth_access_token.rb | 4 +- app/models/user.rb | 1 - db/migrate/20150714055110_create_id_tokens.rb | 3 +- db/schema.rb | 12 +- .../endpoint.rb | 20 ++- .../endpoint_confirmation_point.rb | 10 +- .../endpoint_start_point.rb | 11 ++ .../endpoints/endpoint_start_point.rb | 30 ---- lib/openid_connect/token_endpoint.rb | 1 - .../authorizations_controller_spec.rb | 142 ++++++++++-------- .../protected_resource_endpoint_spec.rb | 28 +++- 16 files changed, 198 insertions(+), 166 deletions(-) rename app/models/{ => openid_connect}/id_token.rb (66%) rename lib/openid_connect/{endpoints => authorization_point}/endpoint.rb (68%) rename lib/openid_connect/{endpoints => authorization_point}/endpoint_confirmation_point.rb (78%) create mode 100644 lib/openid_connect/authorization_point/endpoint_start_point.rb delete mode 100644 lib/openid_connect/endpoints/endpoint_start_point.rb diff --git a/app/controllers/api/v0/base_controller.rb b/app/controllers/api/v0/base_controller.rb index 982800edd..85a3913d1 100644 --- a/app/controllers/api/v0/base_controller.rb +++ b/app/controllers/api/v0/base_controller.rb @@ -3,7 +3,4 @@ class Api::V0::BaseController < ApplicationController before_filter :require_access_token - def authorization - current_token.authorization - end end diff --git a/app/controllers/api/v0/users_controller.rb b/app/controllers/api/v0/users_controller.rb index 57b2c300c..70e04f372 100644 --- a/app/controllers/api/v0/users_controller.rb +++ b/app/controllers/api/v0/users_controller.rb @@ -6,6 +6,6 @@ class Api::V0::UsersController < Api::V0::BaseController private def user - authorization.user + current_token.authorization.user end end diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index 46f226fb2..3d4a55eb9 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -11,31 +11,31 @@ class OpenidConnect::AuthorizationsController < ApplicationController end def create + restore_request_parameters process_authorization_consent(params[:approve]) end private - def request_authorization_consent_form - endpoint = OpenidConnect::Authorization::EndpointStartPoint.new(current_user) - handle_startpoint_response(endpoint) + def request_authorization_consent_form # TODO: Add support for prompt params + if OpenidConnect::Authorization.find_by_client_id_and_user(params[:client_id], current_user) + process_authorization_consent("true") + else + endpoint = OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user) + handle_start_point_response(endpoint) + end end - def handle_startpoint_response(endpoint) + def handle_start_point_response(endpoint) _status, header, response = *endpoint.call(request.env) if response.redirect? redirect_to header["Location"] else - saveParamsAndRenderConsentForm(endpoint) + save_params_and_render_consent_form(endpoint) end end - def process_authorization_consent(approvedString) - endpoint = OpenidConnect::Authorization::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString)) - handle_confirmation_endpoint_response(endpoint) - end - - def saveParamsAndRenderConsentForm(endpoint) + def save_params_and_render_consent_form(endpoint) @o_auth_application, @response_type, @redirect_uri, @scopes, @request_object = *[ endpoint.o_auth_application, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint.request_object ] @@ -43,22 +43,22 @@ class OpenidConnect::AuthorizationsController < ApplicationController render :new end + def save_request_parameters + session[:client_id], session[:response_type], session[:redirect_uri], session[:scopes], session[:request_object], session[:nonce] = + @o_auth_application.client_id, @response_type, @redirect_uri, @scopes.map(&:name), @request_object, params[:nonce] + end + + def process_authorization_consent(approvedString) + endpoint = OpenidConnect::AuthorizationPoint::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString)) + handle_confirmation_endpoint_response(endpoint) + end + def handle_confirmation_endpoint_response(endpoint) - restore_request_parameters(endpoint) _status, header, _response = *endpoint.call(request.env) delete_authorization_session_variables redirect_to header["Location"] end - def restore_request_parameters(endpoint) - req = Rack::Request.new(request.env) - req.update_param("client_id", session[:client_id]) - req.update_param("redirect_uri", session[:redirect_uri]) - req.update_param("response_type", session[:response_type]) - endpoint.scopes, endpoint.request_object, endpoint.nonce = - session[:scopes].map {|scope| Scope.find_by_name(scope) }, session[:request_object], session[:nonce] - end - def delete_authorization_session_variables session.delete(:client_id) session.delete(:response_type) @@ -68,12 +68,17 @@ class OpenidConnect::AuthorizationsController < ApplicationController session.delete(:nonce) end - def save_request_parameters - session[:client_id], session[:response_type], session[:redirect_uri], session[:scopes], session[:request_object], session[:nonce] = - @o_auth_application.client_id, @response_type, @redirect_uri, @scopes.map(&:name), @request_object, params[:nonce] - end - def to_boolean(str) str.downcase == "true" end + + def restore_request_parameters + req = Rack::Request.new(request.env) + req.update_param("client_id", session[:client_id]) + req.update_param("redirect_uri", session[:redirect_uri]) + req.update_param("response_type", session[:response_type]) + req.update_param("scopes", session[:scopes]) + req.update_param("request_object", session[:request_object]) + req.update_param("nonce", session[:nonce]) + end end diff --git a/app/models/openid_connect/authorization.rb b/app/models/openid_connect/authorization.rb index a13a861bd..6793c59ce 100644 --- a/app/models/openid_connect/authorization.rb +++ b/app/models/openid_connect/authorization.rb @@ -2,37 +2,35 @@ class OpenidConnect::Authorization < ActiveRecord::Base belongs_to :user belongs_to :o_auth_application - has_many :scopes, through: :authorization_scopes - has_many :o_auth_access_tokens - - before_validation :setup, on: :create - - validates :refresh_token, presence: true, uniqueness: true validates :user, presence: true, uniqueness: true validates :o_auth_application, presence: true, uniqueness: true - def setup + has_many :scopes, through: :authorization_scopes + has_many :o_auth_access_tokens, dependent: :destroy + has_many :id_tokens + + def generate_refresh_token self.refresh_token = SecureRandom.hex(32) end def create_access_token - OpenidConnect::OAuthAccessToken.create!(authorization: self).bearer_token + o_auth_access_tokens.create!.bearer_token end - # TODO: Actually call this method from token endpoint - def regenerate_refresh_token - self.refresh_token = SecureRandom.hex(32) + def self.find_by_client_id_and_user(client_id, user) + app = OpenidConnect::OAuthApplication.find_by(client_id: client_id) + find_by(o_auth_application: app, user: user) end - def self.find_by_client_id_and_user(app, user) + def self.find_by_app_and_user(app, user) find_by(o_auth_application: app, user: user) end # TODO: Handle creation error def self.find_or_create(client_id, user) app = OpenidConnect::OAuthApplication.find_by(client_id: client_id) - find_by_client_id_and_user(app, user) || create!(user: user, o_auth_application: app) + find_by_app_and_user(app, user) || create!(user: user, o_auth_application: app) end - # TODO: Incomplete class + # TODO: Consider splitting into subclasses by flow type end diff --git a/app/models/id_token.rb b/app/models/openid_connect/id_token.rb similarity index 66% rename from app/models/id_token.rb rename to app/models/openid_connect/id_token.rb index fc65b3751..1f48e3e16 100644 --- a/app/models/id_token.rb +++ b/app/models/openid_connect/id_token.rb @@ -1,12 +1,8 @@ -class IdToken < ActiveRecord::Base - belongs_to :user - belongs_to :o_auth_application +class OpenidConnect::IdToken < ActiveRecord::Base + belongs_to :authorization before_validation :setup, on: :create - validates :user, presence: true - validates :o_auth_application, presence: true - default_scope -> { where("expires_at >= ?", Time.now.utc) } def setup @@ -20,11 +16,11 @@ class IdToken < ActiveRecord::Base def to_response_object(options = {}) claims = { iss: AppConfig.environment.url, - sub: AppConfig.environment.url + o_auth_application.client_id.to_s + user.id.to_s, # TODO: Convert to proper PPID - aud: o_auth_application.client_id, + sub: AppConfig.environment.url + authorization.o_auth_application.client_id.to_s + authorization.user.id.to_s, # TODO: Convert to proper PPID + aud: authorization.o_auth_application.client_id, exp: expires_at.to_i, iat: created_at.to_i, - auth_time: user.current_sign_in_at.to_i, + auth_time: authorization.user.current_sign_in_at.to_i, nonce: nonce, acr: 0 # TODO: Adjust ? } diff --git a/app/models/openid_connect/o_auth_access_token.rb b/app/models/openid_connect/o_auth_access_token.rb index 707bdc223..de46e837f 100644 --- a/app/models/openid_connect/o_auth_access_token.rb +++ b/app/models/openid_connect/o_auth_access_token.rb @@ -1,5 +1,5 @@ class OpenidConnect::OAuthAccessToken < ActiveRecord::Base - belongs_to :authorization, dependent: :delete + belongs_to :authorization has_many :scopes, through: :scope_tokens before_validation :setup, on: :create @@ -17,7 +17,7 @@ class OpenidConnect::OAuthAccessToken < ActiveRecord::Base def bearer_token @bearer_token ||= Rack::OAuth2::AccessToken::Bearer.new( access_token: token, - expires_in: (expires_at - Time.now.utc).to_i + expires_in: (expires_at - Time.now.utc).to_i ) end diff --git a/app/models/user.rb b/app/models/user.rb index 88612b66d..28fa845a2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -79,7 +79,6 @@ class User < ActiveRecord::Base has_many :authorizations, class_name: "OpenidConnect::Authorization" has_many :o_auth_applications, through: :authorizations, class_name: "OpenidConnect::OAuthApplication" has_many :o_auth_access_tokens, through: :authorizations, class_name: "OpenidConnect::OAuthAccessToken" - has_many :id_tokens, class_name: "OpenidConnect::IdToken" before_save :guard_unconfirmed_email, :save_person! diff --git a/db/migrate/20150714055110_create_id_tokens.rb b/db/migrate/20150714055110_create_id_tokens.rb index 0aadbe880..19887d252 100644 --- a/db/migrate/20150714055110_create_id_tokens.rb +++ b/db/migrate/20150714055110_create_id_tokens.rb @@ -1,8 +1,7 @@ class CreateIdTokens < ActiveRecord::Migration def change create_table :id_tokens do |t| - t.belongs_to :user, index: true - t.belongs_to :o_auth_application, index: true + t.belongs_to :authorization, index: true t.datetime :expires_at t.string :nonce diff --git a/db/schema.rb b/db/schema.rb index 2a7e29092..0b6547dbc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -157,16 +157,14 @@ ActiveRecord::Schema.define(version: 20150714055110) do add_index "conversations", ["author_id"], name: "conversations_author_id_fk", using: :btree create_table "id_tokens", force: :cascade do |t| - t.integer "user_id", limit: 4 - t.integer "o_auth_application_id", limit: 4 + t.integer "authorization_id", limit: 4 t.datetime "expires_at" - t.string "nonce", limit: 255 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "nonce", limit: 255 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "id_tokens", ["o_auth_application_id"], name: "index_id_tokens_on_o_auth_application_id", using: :btree - add_index "id_tokens", ["user_id"], name: "index_id_tokens_on_user_id", using: :btree + add_index "id_tokens", ["authorization_id"], name: "index_id_tokens_on_authorization_id", using: :btree create_table "invitation_codes", force: :cascade do |t| t.string "token", limit: 255 diff --git a/lib/openid_connect/endpoints/endpoint.rb b/lib/openid_connect/authorization_point/endpoint.rb similarity index 68% rename from lib/openid_connect/endpoints/endpoint.rb rename to lib/openid_connect/authorization_point/endpoint.rb index 9a1b35eca..9d31c4957 100644 --- a/lib/openid_connect/endpoints/endpoint.rb +++ b/lib/openid_connect/authorization_point/endpoint.rb @@ -1,5 +1,5 @@ module OpenidConnect - module Authorization + module AuthorizationPoint class Endpoint attr_accessor :app, :user, :o_auth_application, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object, :nonce @@ -20,6 +20,8 @@ module OpenidConnect def build_attributes(req, res) build_client(req) build_redirect_uri(req, res) + verify_nonce(req, res) + build_scopes(req) end def handle_response_type(req, res) @@ -35,6 +37,22 @@ module OpenidConnect def build_redirect_uri(req, res) res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@o_auth_application.redirect_uris) end + + def verify_nonce(req, res) + if res.protocol_params_location == :fragment && req.nonce.blank? + req.invalid_request! "nonce required" + end + end + + def build_scopes(req) + @scopes = req.scope.map {|scope| + OpenidConnect::Scope.where(name: scope).first.tap do |scope| + req.invalid_scope! "Unknown scope: #{scope}" unless scope + end + } + end + + # TODO: buildResponseType(req) end end end diff --git a/lib/openid_connect/endpoints/endpoint_confirmation_point.rb b/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb similarity index 78% rename from lib/openid_connect/endpoints/endpoint_confirmation_point.rb rename to lib/openid_connect/authorization_point/endpoint_confirmation_point.rb index 2400d6db1..7b91d0123 100644 --- a/lib/openid_connect/endpoints/endpoint_confirmation_point.rb +++ b/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -1,16 +1,11 @@ module OpenidConnect - module Endpoints + module AuthorizationPoint class EndpointConfirmationPoint < Endpoint def initialize(current_user, approved=false) super(current_user) @approved = approved end - def build_attributes(req, res) - super(req, res) - # TODO: buildResponseType(req) - end - def handle_response_type(req, res) handle_approval(@approved, req, res) end @@ -24,9 +19,10 @@ module OpenidConnect end def approved!(req, res) + auth = OpenidConnect::Authorization.find_or_create(req.client_id, @user) response_types = Array(req.response_type) if response_types.include?(:id_token) - id_token = @user.id_tokens.create!(o_auth_application: o_auth_application, nonce: @nonce) + id_token = auth.id_tokens.create!(nonce: req.nonce) options = %i(code access_token).map{|option| ["res.#{option}", res.respond_to?(option) ? res.option : nil]}.to_h res.id_token = id_token.to_jwt(options) # TODO: Add support for request object diff --git a/lib/openid_connect/authorization_point/endpoint_start_point.rb b/lib/openid_connect/authorization_point/endpoint_start_point.rb new file mode 100644 index 000000000..94fa20770 --- /dev/null +++ b/lib/openid_connect/authorization_point/endpoint_start_point.rb @@ -0,0 +1,11 @@ +module OpenidConnect + module AuthorizationPoint + class EndpointStartPoint < Endpoint + def handle_response_type(req, res) + @response_type = req.response_type + end + + # TODO: buildRequestObject(req) + end + end +end diff --git a/lib/openid_connect/endpoints/endpoint_start_point.rb b/lib/openid_connect/endpoints/endpoint_start_point.rb deleted file mode 100644 index 0a167578f..000000000 --- a/lib/openid_connect/endpoints/endpoint_start_point.rb +++ /dev/null @@ -1,30 +0,0 @@ -module OpenidConnect - module Endpoints - class EndpointStartPoint < Endpoint - def handle_response_type(req, res) - @response_type = req.response_type - end - - def build_attributes(req, res) - super(req, res) - verify_nonce(req, res) - build_scopes(req) - # TODO: buildRequestObject(req) - end - - def verify_nonce(req, res) - if res.protocol_params_location == :fragment && req.nonce.blank? - req.invalid_request! "nonce required" - end - end - - def build_scopes(req) - @scopes = req.scope.map {|scope| - OpenidConnect::Scope.where(name: scope).first.tap do |scope| - req.invalid_scope! "Unknown scope: #{scope}" unless scope - end - } - end - end - end -end diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb index 14dcfa94c..a19d7766c 100644 --- a/lib/openid_connect/token_endpoint.rb +++ b/lib/openid_connect/token_endpoint.rb @@ -55,6 +55,5 @@ module OpenidConnect def app_valid?(o_auth_app, req) o_auth_app.client_secret == req.client_secret end - end end diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb index a8f9d4f60..08b65bc17 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -14,76 +14,100 @@ describe OpenidConnect::AuthorizationsController, type: :controller do end describe "#new" do - context "when valid parameters are passed" do - render_views - context "as GET request" do - it "should return a form page" do - get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", - scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) - expect(response.body).to match("Diaspora Test Client") + context "when not yet authorized" do + context "when valid parameters are passed" do + render_views + context "as GET request" do + it "should return a form page" do + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) + expect(response.body).to match("Diaspora Test Client") + end + end + + context "as POST request" do + it "should return a form page" do + post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) + expect(response.body).to match("Diaspora Test Client") + end end end - context "as POST request" do - it "should return a form page" do + context "when client id is missing" do + it "should return an bad request error" do + post :new, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) + expect(response.body).to match("bad_request") + end + end + + context "when redirect uri is missing" do + context "when only one redirect URL is pre-registered" do + it "should return a form pager" do + # Note this intentionally behavior diverts from OIDC spec http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + # When client has only one redirect uri registered, only that redirect uri can be used. Hence, + # we should implicitly assume the client wants to use that registered URI. + # See https://github.com/nov/rack-oauth2/blob/master/lib/rack/oauth2/server/authorize.rb#L63 + post :new, client_id: client.client_id, response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) + expect(response.body).to match("Diaspora Test Client") + end + end + end + + context "when multiple redirect URLs are pre-registered" do + it "should return an invalid request error" do + post :new, client_id: client_with_multiple_redirects.client_id, response_type: "id_token", + scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) + expect(response.body).to match("bad_request") + end + end + + context "when redirect URI does not match pre-registered URIs" do + it "should return an invalid request error" do + post :new, client_id: client.client_id, redirect_uri: "http://localhost:2000/", + response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16) + expect(response.body).to match("bad_request") + end + end + + context "when an unsupported scope is passed in" do + it "should return an invalid scope error" do post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", - scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) - expect(response.body).to match("Diaspora Test Client") + scope: "random", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) + expect(response.body).to match("error=invalid_scope") + end + end + + context "when nonce is missing" do + it "should return an invalid request error" do + post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", state: SecureRandom.hex(16) + expect(response.location).to match("error=invalid_request") end end end + context "when already authorized" do + let!(:auth) { OpenidConnect::Authorization.find_or_create(client.client_id, alice) } - context "when client id is missing" do - it "should return an bad request error" do - post :new, redirect_uri: "http://localhost:3000/", response_type: "id_token", - scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) - expect(response.body).to match("bad_request") - end - end - - context "when redirect uri is missing" do - context "when only one redirect URL is pre-registered" do - it "should return a form pager" do - # Note this intentionally behavior diverts from OIDC spec http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest - # When client has only one redirect uri registered, only that redirect uri can be used. Hence, - # we should implicitly assume the client wants to use that registered URI. - # See https://github.com/nov/rack-oauth2/blob/master/lib/rack/oauth2/server/authorize.rb#L63 - post :new, client_id: client.client_id, response_type: "id_token", - scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) - expect(response.body).to match("Diaspora Test Client") + context "when valid parameters are passed" do + before do + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: 413_093_098_3, state: 413_093_098_3 end - end - end - context "when multiple redirect URLs are pre-registered" do - it "should return an invalid request error" do - post :new, client_id: client_with_multiple_redirects.client_id, response_type: "id_token", - scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) - expect(response.body).to match("bad_request") - end - end + it "should return the id token in a fragment" do + expect(response.location).to have_content("id_token=") + encoded_id_token = response.location[/(?<=id_token=)[^&]+/] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key + expect(decoded_token.nonce).to eq("4130930983") + expect(decoded_token.exp).to be > Time.now.utc.to_i + end - context "when redirect URI does not match pre-registered URIs" do - it "should return an invalid request error" do - post :new, client_id: client.client_id, redirect_uri: "http://localhost:2000/", - response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16) - expect(response.body).to match("bad_request") - end - end - - context "when an unsupported scope is passed in" do - it "should return an invalid scope error" do - post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", - scope: "random", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) - expect(response.body).to match("error=invalid_scope") - end - end - - context "when nonce is missing" do - it "should return an invalid request error" do - post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", - response_type: "id_token", scope: "openid", state: SecureRandom.hex(16) - expect(response.location).to match("error=invalid_request") + it "should return the passed in state" do + expect(response.location).to have_content("state=4130930983") + end end end end diff --git a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb index bb95fda69..2ceab0972 100644 --- a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb @@ -35,16 +35,38 @@ describe OpenidConnect::ProtectedResourceEndpoint, type: :request do end context "when an invalid access token is provided" do - it "should respond with a 401 Unauthorized response" do + before do get "/api/v0/user/", access_token: invalid_token + end + + it "should respond with a 401 Unauthorized response" do expect(response.status).to be(401) end + it "should have an auth-scheme value of Bearer" do - get "/api/v0/user/", access_token: invalid_token expect(response.headers["WWW-Authenticate"]).to include("Bearer") end + + it "should contain an invalid_token error" do + expect(response.body).to include("invalid_token") + end + end + + context "when authorization has been destroyed" do + before do + auth.destroy + get "/api/v0/user/", access_token: access_token + end + + it "should respond with a 401 Unauthorized response" do + expect(response.status).to be(401) + end + + it "should have an auth-scheme value of Bearer" do + expect(response.headers["WWW-Authenticate"]).to include("Bearer") + end + it "should contain an invalid_token error" do - get "/api/v0/user/", access_token: invalid_token expect(response.body).to include("invalid_token") end end From ee9ac06e1a6c1d6cdccaee15d758e0b90e899df7 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Thu, 16 Jul 2015 01:53:10 +0900 Subject: [PATCH 022/105] Add support for access tokens in implicit flow Squashed commits: [7dbf618] Use Rail's find_or_create_by method --- .../authorizations_controller.rb | 2 +- app/models/openid_connect/authorization.rb | 21 ++--- .../openid_connect/o_auth_access_token.rb | 2 +- .../openid_connect/o_auth_application.rb | 2 +- .../endpoint_confirmation_point.rb | 13 ++-- .../authorizations_controller_spec.rb | 76 +++++++++++++------ 6 files changed, 73 insertions(+), 43 deletions(-) diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index 3d4a55eb9..febf6247a 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -76,7 +76,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController req = Rack::Request.new(request.env) req.update_param("client_id", session[:client_id]) req.update_param("redirect_uri", session[:redirect_uri]) - req.update_param("response_type", session[:response_type]) + req.update_param("response_type", session[:response_type].respond_to?(:map) ? session[:response_type].map(&:to_s).join(" ") : session[:response_type]) req.update_param("scopes", session[:scopes]) req.update_param("request_object", session[:request_object]) req.update_param("nonce", session[:nonce]) diff --git a/app/models/openid_connect/authorization.rb b/app/models/openid_connect/authorization.rb index 6793c59ce..c91aa2cc6 100644 --- a/app/models/openid_connect/authorization.rb +++ b/app/models/openid_connect/authorization.rb @@ -2,12 +2,12 @@ class OpenidConnect::Authorization < ActiveRecord::Base belongs_to :user belongs_to :o_auth_application - validates :user, presence: true, uniqueness: true - validates :o_auth_application, presence: true, uniqueness: true + validates :user, presence: true + validates :o_auth_application, presence: true has_many :scopes, through: :authorization_scopes has_many :o_auth_access_tokens, dependent: :destroy - has_many :id_tokens + has_many :id_tokens, dependent: :destroy def generate_refresh_token self.refresh_token = SecureRandom.hex(32) @@ -15,6 +15,11 @@ class OpenidConnect::Authorization < ActiveRecord::Base def create_access_token o_auth_access_tokens.create!.bearer_token + # TODO: Add support for request object + end + + def create_id_token(nonce) + id_tokens.create!(nonce: nonce) end def self.find_by_client_id_and_user(client_id, user) @@ -22,15 +27,5 @@ class OpenidConnect::Authorization < ActiveRecord::Base find_by(o_auth_application: app, user: user) end - def self.find_by_app_and_user(app, user) - find_by(o_auth_application: app, user: user) - end - - # TODO: Handle creation error - def self.find_or_create(client_id, user) - app = OpenidConnect::OAuthApplication.find_by(client_id: client_id) - find_by_app_and_user(app, user) || create!(user: user, o_auth_application: app) - end - # TODO: Consider splitting into subclasses by flow type end diff --git a/app/models/openid_connect/o_auth_access_token.rb b/app/models/openid_connect/o_auth_access_token.rb index de46e837f..04f423f4d 100644 --- a/app/models/openid_connect/o_auth_access_token.rb +++ b/app/models/openid_connect/o_auth_access_token.rb @@ -5,7 +5,7 @@ class OpenidConnect::OAuthAccessToken < ActiveRecord::Base before_validation :setup, on: :create validates :token, presence: true, uniqueness: true - validates :authorization, presence: true, uniqueness: true + validates :authorization, presence: true scope :valid, ->(time) { where("expires_at >= ?", time) } diff --git a/app/models/openid_connect/o_auth_application.rb b/app/models/openid_connect/o_auth_application.rb index 2ba85ba0b..482f0ad24 100644 --- a/app/models/openid_connect/o_auth_application.rb +++ b/app/models/openid_connect/o_auth_application.rb @@ -16,7 +16,7 @@ class OpenidConnect::OAuthApplication < ActiveRecord::Base class << self def available_response_types - ["id_token"] + ["id_token", "id_token token"] end def register!(registrar) diff --git a/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb index 7b91d0123..cdb4d9474 100644 --- a/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -18,14 +18,17 @@ module OpenidConnect end end + # TODO: Add support for request object and auth code def approved!(req, res) - auth = OpenidConnect::Authorization.find_or_create(req.client_id, @user) + auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: @o_auth_application, user: @user) response_types = Array(req.response_type) + if response_types.include?(:token) + res.access_token = auth.create_access_token + end if response_types.include?(:id_token) - id_token = auth.id_tokens.create!(nonce: req.nonce) - options = %i(code access_token).map{|option| ["res.#{option}", res.respond_to?(option) ? res.option : nil]}.to_h - res.id_token = id_token.to_jwt(options) - # TODO: Add support for request object + id_token = auth.create_id_token(req.nonce) + access_token_value = res.respond_to?(:access_token) ? res.access_token : nil + res.id_token = id_token.to_jwt(code: nil, access_token: access_token_value) end res.approve! end diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb index 08b65bc17..bb597254d 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -89,7 +89,7 @@ describe OpenidConnect::AuthorizationsController, type: :controller do end end context "when already authorized" do - let!(:auth) { OpenidConnect::Authorization.find_or_create(client.client_id, alice) } + let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice) } context "when valid parameters are passed" do before do @@ -113,41 +113,73 @@ describe OpenidConnect::AuthorizationsController, type: :controller do end describe "#create" do - before do - get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", - scope: "openid", nonce: 418_093_098_3, state: 418_093_098_3 - end - context "when authorization is approved" do + context "when id_token token" do before do - post :create, approve: "true" + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token token", + scope: "openid", nonce: 418_093_098_3, state: 418_093_098_3 end - it "should return the id token in a fragment" do - expect(response.location).to have_content("id_token=") - encoded_id_token = response.location[/(?<=id_token=)[^&]+/] - decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key - expect(decoded_token.nonce).to eq("4180930983") - expect(decoded_token.exp).to be > Time.now.utc.to_i - end + context "when authorization is approved" do + before do + post :create, approve: "true" + end - it "should return the passed in state" do - expect(response.location).to have_content("state=4180930983") + it "should return the id token in a fragment" do + encoded_id_token = response.location[/(?<=id_token=)[^&]+/] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key + expect(decoded_token.nonce).to eq("4180930983") + expect(decoded_token.exp).to be > Time.now.utc.to_i + end + + it "should return a valid access token in a fragment" do + encoded_id_token = response.location[/(?<=id_token=)[^&]+/] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key + access_token = response.location[/(?<=access_token=)[^&]+/] + access_token_check_num = UrlSafeBase64.encode64(OpenSSL::Digest::SHA256.digest(access_token)[0, 128 / 8]) + expect(decoded_token.at_hash).to eq(access_token_check_num) + end end end - context "when authorization is denied" do + context "when id_token" do before do - post :create, approve: "false" + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: 418_093_098_3, state: 418_093_098_3 end - it "should return an error in the fragment" do - expect(response.location).to have_content("error=") + context "when authorization is approved" do + before do + post :create, approve: "true" + end + + it "should return the id token in a fragment" do + expect(response.location).to have_content("id_token=") + encoded_id_token = response.location[/(?<=id_token=)[^&]+/] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key + expect(decoded_token.nonce).to eq("4180930983") + expect(decoded_token.exp).to be > Time.now.utc.to_i + end + + it "should return the passed in state" do + expect(response.location).to have_content("state=4180930983") + end end - it "should NOT contain a id token in the fragment" do - expect(response.location).to_not have_content("id_token=") + context "when authorization is denied" do + before do + post :create, approve: "false" + end + + it "should return an error in the fragment" do + expect(response.location).to have_content("error=") + end + + it "should NOT contain a id token in the fragment" do + expect(response.location).to_not have_content("id_token=") + end end end + end end From 7b80a7408da614fd78f67da46a7e822271aab3cc Mon Sep 17 00:00:00 2001 From: theworldbright Date: Thu, 16 Jul 2015 17:12:53 +0900 Subject: [PATCH 023/105] Add integration tests for implicit flow Squashed commits: [d5001fe] Refactor [8d8a23f] Add test for when authorization is denied [659fc56] Adjust password flow integration test --- app/models/openid_connect/authorization.rb | 1 + app/models/user.rb | 4 -- ...150613202109_create_o_auth_applications.rb | 4 -- ...50614134031_create_o_auth_access_tokens.rb | 2 +- features/desktop/oauth_password_flow.feature | 6 +-- features/desktop/oidc_implicit_flow.feature | 26 ++++++++++++ .../step_definitions/implicit_flow_steps.rb | 41 +++++++++++++++++++ ...openid_steps.rb => password_flow_steps.rb} | 6 +-- lib/openid_connect/token_endpoint.rb | 10 ++--- public/docs/v0/index.html | 17 -------- public/docs/v0/style.css | 5 --- .../authorizations_controller_spec.rb | 2 + 12 files changed, 82 insertions(+), 42 deletions(-) create mode 100644 features/desktop/oidc_implicit_flow.feature create mode 100644 features/step_definitions/implicit_flow_steps.rb rename features/step_definitions/{openid_steps.rb => password_flow_steps.rb} (88%) delete mode 100644 public/docs/v0/index.html delete mode 100644 public/docs/v0/style.css diff --git a/app/models/openid_connect/authorization.rb b/app/models/openid_connect/authorization.rb index c91aa2cc6..abbe8c090 100644 --- a/app/models/openid_connect/authorization.rb +++ b/app/models/openid_connect/authorization.rb @@ -4,6 +4,7 @@ class OpenidConnect::Authorization < ActiveRecord::Base validates :user, presence: true validates :o_auth_application, presence: true + validates :user, uniqueness: {scope: :o_auth_application} has_many :scopes, through: :authorization_scopes has_many :o_auth_access_tokens, dependent: :destroy diff --git a/app/models/user.rb b/app/models/user.rb index 28fa845a2..3766456bc 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -602,10 +602,6 @@ class User < ActiveRecord::Base end end - def find_authorization_by_client_id(client_id) - OpenidConnect::Authorization.find_by_client_id_and_user client_id, self - end - private def clearable_fields diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index c5f44bfef..0e36a7006 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -10,8 +10,4 @@ class CreateOAuthApplications < ActiveRecord::Migration t.timestamps null: false end end - - def self.down - drop_table :o_auth_applications - end end diff --git a/db/migrate/20150614134031_create_o_auth_access_tokens.rb b/db/migrate/20150614134031_create_o_auth_access_tokens.rb index 5084d61e5..8bd619142 100644 --- a/db/migrate/20150614134031_create_o_auth_access_tokens.rb +++ b/db/migrate/20150614134031_create_o_auth_access_tokens.rb @@ -1,5 +1,5 @@ class CreateOAuthAccessTokens < ActiveRecord::Migration - def self.up + def change create_table :o_auth_access_tokens do |t| t.belongs_to :authorization, index: true t.string :token diff --git a/features/desktop/oauth_password_flow.feature b/features/desktop/oauth_password_flow.feature index 2829ac296..7fa59c4fd 100644 --- a/features/desktop/oauth_password_flow.feature +++ b/features/desktop/oauth_password_flow.feature @@ -4,17 +4,17 @@ Feature: Access protected resources using password flow Scenario: Invalid credentials to token endpoint When I register a new client - And I send a post request from that client to the token endpoint using invalid credentials + And I send a post request from that client to the password flow token endpoint using invalid credentials Then I should receive an "invalid_grant" error Scenario: Invalid bearer tokens sent When I register a new client - And I send a post request from that client to the token endpoint using "kent"'s credentials + And I send a post request from that client to the password flow token endpoint using "kent"'s credentials And I use invalid bearer tokens to access user info Then I should receive an "invalid_token" error Scenario: Valid password flow When I register a new client - And I send a post request from that client to the token endpoint using "kent"'s credentials + And I send a post request from that client to the password flow token endpoint using "kent"'s credentials And I use received valid bearer tokens to access user info Then I should receive "kent"'s id, username, and email diff --git a/features/desktop/oidc_implicit_flow.feature b/features/desktop/oidc_implicit_flow.feature new file mode 100644 index 000000000..92bf59d5a --- /dev/null +++ b/features/desktop/oidc_implicit_flow.feature @@ -0,0 +1,26 @@ +@javascript +Feature: Access protected resources using implicit flow + Background: + Given a user with username "kent" + And the OpenID scope exists + + Scenario: Invalid client id to auth endpoint + When I register a new client + And I send a post request from that client to the implicit flow authorization endpoint using a invalid client id + And I sign in as "kent@kent.kent" + Then I should see an "bad_request" error + + Scenario: Application is denied authorization + When I register a new client + And I send a post request from that client to the implicit flow authorization endpoint + And I sign in as "kent@kent.kent" + And I deny authorization to the client + Then I should not see any tokens in the redirect url + + Scenario: Application is authorized + When I register a new client + And I send a post request from that client to the implicit flow authorization endpoint + And I sign in as "kent@kent.kent" + And I give my consent and authorize the client + And I parse the bearer tokens and use it to access user info + Then I should receive "kent"'s id, username, and email diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb new file mode 100644 index 000000000..2ea9fdfa0 --- /dev/null +++ b/features/step_definitions/implicit_flow_steps.rb @@ -0,0 +1,41 @@ +Given(/^the OpenID scope exists$/) do + OpenidConnect::Scope.create(name: "openid") +end + +Given /^I send a post request from that client to the implicit flow authorization endpoint$/ do + client_json = JSON.parse(last_response.body) + auth_endpoint_url = "/openid_connect/authorizations/new" + visit auth_endpoint_url + "?client_id=" + client_json["o_auth_application"]["client_id"] + "&redirect_uri=" + "http://localhost:3000" + + "&response_type=id_token token" + "&scope=openid" + "&nonce=hello" + "&state=hi" +end + +Given /^I send a post request from that client to the implicit flow authorization endpoint using a invalid client id/ do + auth_endpoint_url = "/openid_connect/authorizations/new" + visit auth_endpoint_url + "?client_id=randomid" + "&redirect_uri=" + "http://localhost:3000" + + "&response_type=id_token token" + "&scope=openid" + "&nonce=hello" + "&state=hi" +end + +When /^I give my consent and authorize the client$/ do + click_button "Approve" +end + +When /^I deny authorization to the client$/ do + click_button "Deny" +end + +Then /^I should not see any tokens in the redirect url$/ do + access_token = current_url[/(?<=access_token=)[^&]+/] + id_token = current_url[/(?<=access_token=)[^&]+/] + expect(access_token).to eq(nil) + expect(id_token).to eq(nil) +end + +When /^I parse the bearer tokens and use it to access user info$/ do + access_token = current_url[/(?<=access_token=)[^&]+/] + user_info_endpoint_url = "/api/v0/user/" + get user_info_endpoint_url, access_token: access_token +end + +Then /^I should see an "([^\"]*)" error$/ do |error_message| + expect(page).to have_content(error_message) +end diff --git a/features/step_definitions/openid_steps.rb b/features/step_definitions/password_flow_steps.rb similarity index 88% rename from features/step_definitions/openid_steps.rb rename to features/step_definitions/password_flow_steps.rb index 3aa5bdf36..2dcbf7b3f 100644 --- a/features/step_definitions/openid_steps.rb +++ b/features/step_definitions/password_flow_steps.rb @@ -1,9 +1,9 @@ When /^I register a new client$/ do client_registration_url = "/openid_connect/clients" - post client_registration_url, redirect_uris: ["http://localhost:3000"] # Not actually used + post client_registration_url, redirect_uris: ["http://localhost:3000"] end -Given /^I send a post request from that client to the token endpoint using "([^\"]*)"'s credentials$/ do |username| +Given /^I send a post request from that client to the password flow token endpoint using "([^\"]*)"'s credentials$/ do |username| client_json = JSON.parse(last_response.body) user = User.find_by(username: username) token_endpoint_url = "/openid_connect/access_tokens" @@ -13,7 +13,7 @@ Given /^I send a post request from that client to the token endpoint using "([^\ client_secret: client_json["o_auth_application"]["client_secret"] end -Given /^I send a post request from that client to the token endpoint using invalid credentials$/ do +Given /^I send a post request from that client to the password flow token endpoint using invalid credentials$/ do client_json = JSON.parse(last_response.body) token_endpoint_url = "/openid_connect/access_tokens" post token_endpoint_url, grant_type: "password", username: "bob", password: "wrongpassword", diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb index a19d7766c..116a5c67b 100644 --- a/lib/openid_connect/token_endpoint.rb +++ b/lib/openid_connect/token_endpoint.rb @@ -7,17 +7,17 @@ module OpenidConnect @app = Rack::OAuth2::Server::Token.new do |req, res| o_auth_app = retrieve_client(req) if app_valid?(o_auth_app, req) - handle_flows(req, res) + handle_flows(o_auth_app, req, res) else req.invalid_client! end end end - def handle_flows(req, res) + def handle_flows(o_auth_app, req, res) case req.grant_type when :password - handle_password_flow(req, res) + handle_password_flow(o_auth_app, req, res) when :refresh_token handle_refresh_flow(req, res) else @@ -25,11 +25,11 @@ module OpenidConnect end end - def handle_password_flow(req, res) + def handle_password_flow(o_auth_app, req, res) user = User.find_for_database_authentication(username: req.username) if user if user.valid_password?(req.password) - auth = OpenidConnect::Authorization.find_or_create(req.client_id, user) + auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: o_auth_app, user: user) res.access_token = auth.create_access_token else req.invalid_grant! diff --git a/public/docs/v0/index.html b/public/docs/v0/index.html deleted file mode 100644 index 08b6b913f..000000000 --- a/public/docs/v0/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - Documentation for v0 - - - -
-
-

API Operations

-
-
-

Documentation for v0

-
-
- - diff --git a/public/docs/v0/style.css b/public/docs/v0/style.css deleted file mode 100644 index ea6e1ce9c..000000000 --- a/public/docs/v0/style.css +++ /dev/null @@ -1,5 +0,0 @@ -body {margin: 0; background-color: #fff; color: #000; font-family: Arial,sans-serif;} -content {margin-left: 200px;} -content h1 {text-align: center;} -operations {float: left; width: 200px; border-right: 1px solid #ccc;} -operations h3 {text-align: center;} diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb index bb597254d..1c8e3655b 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -7,6 +7,8 @@ describe OpenidConnect::AuthorizationsController, type: :controller do name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/", "http://localhost/"]) end + # TODO: jhass - "Might want to setup some factories in spec/factories.rb, see factory_girl's docs." + before do sign_in :user, alice allow(@controller).to receive(:current_user).and_return(alice) From cc28199555a2cfb5c72912ab4f9dc1b5944958a5 Mon Sep 17 00:00:00 2001 From: augier Date: Thu, 16 Jul 2015 20:38:56 +0200 Subject: [PATCH 024/105] Fixing hounds remarks --- .../authorizations_controller.rb | 12 ++++-- .../openid_connect/discovery_controller.rb | 38 +++++++++---------- app/models/openid_connect/id_token.rb | 18 ++++----- .../openid_connect/o_auth_access_token.rb | 2 +- app/models/user.rb | 10 ++--- .../step_definitions/implicit_flow_steps.rb | 14 +++++-- .../authorization_point/endpoint.rb | 2 +- .../endpoint_start_point.rb | 2 +- .../authorizations_controller_spec.rb | 18 +++++---- .../id_tokens_controller_spec.rb | 2 +- 10 files changed, 66 insertions(+), 52 deletions(-) diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index febf6247a..0cf7faeb4 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -1,7 +1,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| - logger.info e.backtrace[0,10].join("\n") - render json: { error: e.message || :error, status: e.status } + logger.info e.backtrace[0, 10].join("\n") + render json: {error: e.message || :error, status: e.status} end before_action :authenticate_user! @@ -44,8 +44,12 @@ class OpenidConnect::AuthorizationsController < ApplicationController end def save_request_parameters - session[:client_id], session[:response_type], session[:redirect_uri], session[:scopes], session[:request_object], session[:nonce] = - @o_auth_application.client_id, @response_type, @redirect_uri, @scopes.map(&:name), @request_object, params[:nonce] + session[:client_id] = @o_auth_application.client_id + session[:response_type] = @response_type + session[:redirect_uri] = @redirect_uri + session[:scopes] = @scopes.map(&:name) + session[:request_object] = @request_object + session[:nonce] = params[:nonce] end def process_authorization_consent(approvedString) diff --git a/app/controllers/openid_connect/discovery_controller.rb b/app/controllers/openid_connect/discovery_controller.rb index d2b6745f1..a11207b8d 100644 --- a/app/controllers/openid_connect/discovery_controller.rb +++ b/app/controllers/openid_connect/discovery_controller.rb @@ -1,30 +1,30 @@ class OpenidConnect::DiscoveryController < ApplicationController def webfinger jrd = { - links: [{ - rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, - href: File.join(root_url, "openid_connect") - }] - } + links: [{ + rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, + href: File.join(root_url, "openid_connect") + }] + } jrd[:subject] = params[:resource] if params[:resource].present? render json: jrd, content_type: "application/jrd+json" end def configuration render json: OpenIDConnect::Discovery::Provider::Config::Response.new( - issuer: root_url, - registration_endpoint: openid_connect_clients_url, - authorization_endpoint: new_openid_connect_authorization_url, - token_endpoint: openid_connect_access_tokens_url, - userinfo_endpoint: api_v0_user_url, - jwks_uri: File.join(root_url, "openid_connect", "jwks.json"), - scopes_supported: Scope.pluck(:name), - response_types_supported: OAuthApplication.available_response_types, - request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), - subject_types_supported: %w(public pairwise), - id_token_signing_alg_values_supported: %i(RS256), - token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), - # TODO: claims_supported: ["sub", "iss", "name", "email"] - ) + issuer: root_url, + registration_endpoint: openid_connect_clients_url, + authorization_endpoint: new_openid_connect_authorization_url, + token_endpoint: openid_connect_access_tokens_url, + userinfo_endpoint: api_v0_user_url, + jwks_uri: File.join(root_url, "openid_connect", "jwks.json"), + scopes_supported: Scope.pluck(:name), + response_types_supported: OAuthApplication.available_response_types, + request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), + subject_types_supported: %w(public pairwise), + id_token_signing_alg_values_supported: %i(RS256), + token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), + # TODO: claims_supported: ["sub", "iss", "name", "email"] + ) end end diff --git a/app/models/openid_connect/id_token.rb b/app/models/openid_connect/id_token.rb index 1f48e3e16..cd8c4d9c3 100644 --- a/app/models/openid_connect/id_token.rb +++ b/app/models/openid_connect/id_token.rb @@ -9,20 +9,20 @@ class OpenidConnect::IdToken < ActiveRecord::Base self.expires_at = 30.minutes.from_now end - def to_jwt(options = {}) + def to_jwt(options={}) to_response_object(options).to_jwt OpenidConnect::IdTokenConfig.private_key end - def to_response_object(options = {}) + def to_response_object(options={}) claims = { - iss: AppConfig.environment.url, - sub: AppConfig.environment.url + authorization.o_auth_application.client_id.to_s + authorization.user.id.to_s, # TODO: Convert to proper PPID - aud: authorization.o_auth_application.client_id, - exp: expires_at.to_i, - iat: created_at.to_i, + iss: AppConfig.environment.url, + sub: AppConfig.environment.url + authorization.o_auth_application.client_id.to_s + authorization.user.id.to_s, # TODO: Convert to proper PPID + aud: authorization.o_auth_application.client_id, + exp: expires_at.to_i, + iat: created_at.to_i, auth_time: authorization.user.current_sign_in_at.to_i, - nonce: nonce, - acr: 0 # TODO: Adjust ? + nonce: nonce, + acr: 0 # TODO: Adjust ? } id_token = OpenIDConnect::ResponseObject::IdToken.new(claims) id_token.code = options[:code] if options[:code] diff --git a/app/models/openid_connect/o_auth_access_token.rb b/app/models/openid_connect/o_auth_access_token.rb index 04f423f4d..cc72dc3d9 100644 --- a/app/models/openid_connect/o_auth_access_token.rb +++ b/app/models/openid_connect/o_auth_access_token.rb @@ -17,7 +17,7 @@ class OpenidConnect::OAuthAccessToken < ActiveRecord::Base def bearer_token @bearer_token ||= Rack::OAuth2::AccessToken::Bearer.new( access_token: token, - expires_in: (expires_at - Time.now.utc).to_i + expires_in: (expires_at - Time.now.utc).to_i ) end diff --git a/app/models/user.rb b/app/models/user.rb index 3766456bc..c77a75e1c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -605,10 +605,10 @@ class User < ActiveRecord::Base private def clearable_fields - self.attributes.keys - %w(id username encrypted_password created_at updated_at locked_at - serialized_private_key getting_started - disable_mail show_community_spotlight_in_stream - strip_exif email remove_after export exporting exported_at - exported_photos_file exporting_photos exported_photos_at) + attributes.keys - %w(id username encrypted_password created_at updated_at locked_at + serialized_private_key getting_started + disable_mail show_community_spotlight_in_stream + strip_exif email remove_after export exporting exported_at + exported_photos_file exporting_photos exported_photos_at) end end diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb index 2ea9fdfa0..cc0f63e34 100644 --- a/features/step_definitions/implicit_flow_steps.rb +++ b/features/step_definitions/implicit_flow_steps.rb @@ -1,3 +1,11 @@ +o_auth_query_params = %i( + redirect_uri=http://localhost:3000 + response_type=id_token token + scope=openid + nonce=hello + state=hi +).join("&") + Given(/^the OpenID scope exists$/) do OpenidConnect::Scope.create(name: "openid") end @@ -5,14 +13,12 @@ end Given /^I send a post request from that client to the implicit flow authorization endpoint$/ do client_json = JSON.parse(last_response.body) auth_endpoint_url = "/openid_connect/authorizations/new" - visit auth_endpoint_url + "?client_id=" + client_json["o_auth_application"]["client_id"] + "&redirect_uri=" + "http://localhost:3000" + - "&response_type=id_token token" + "&scope=openid" + "&nonce=hello" + "&state=hi" + visit "#{auth_endpoint_url}?client_id=#{client_json["o_auth_application"]["client_id"]}&#{o_auth_query_params}" end Given /^I send a post request from that client to the implicit flow authorization endpoint using a invalid client id/ do auth_endpoint_url = "/openid_connect/authorizations/new" - visit auth_endpoint_url + "?client_id=randomid" + "&redirect_uri=" + "http://localhost:3000" + - "&response_type=id_token token" + "&scope=openid" + "&nonce=hello" + "&state=hi" + visit "#{auth_endpoint_url}?client_id=randomid&#{o_auth_query_params}" end When /^I give my consent and authorize the client$/ do diff --git a/lib/openid_connect/authorization_point/endpoint.rb b/lib/openid_connect/authorization_point/endpoint.rb index 9d31c4957..839bed9c7 100644 --- a/lib/openid_connect/authorization_point/endpoint.rb +++ b/lib/openid_connect/authorization_point/endpoint.rb @@ -24,7 +24,7 @@ module OpenidConnect build_scopes(req) end - def handle_response_type(req, res) + def handle_response_type(_req, _res) # Implemented by subclass end diff --git a/lib/openid_connect/authorization_point/endpoint_start_point.rb b/lib/openid_connect/authorization_point/endpoint_start_point.rb index 94fa20770..3af4274e7 100644 --- a/lib/openid_connect/authorization_point/endpoint_start_point.rb +++ b/lib/openid_connect/authorization_point/endpoint_start_point.rb @@ -1,7 +1,7 @@ module OpenidConnect module AuthorizationPoint class EndpointStartPoint < Endpoint - def handle_response_type(req, res) + def handle_response_type(req, _res) @response_type = req.response_type end diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb index 1c8e3655b..706bff247 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -1,7 +1,9 @@ require "spec_helper" describe OpenidConnect::AuthorizationsController, type: :controller do - let!(:client) { OpenidConnect::OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) } + let!(:client) do + OpenidConnect::OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) + end let!(:client_with_multiple_redirects) do OpenidConnect::OAuthApplication.create!( name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/", "http://localhost/"]) @@ -102,7 +104,8 @@ describe OpenidConnect::AuthorizationsController, type: :controller do it "should return the id token in a fragment" do expect(response.location).to have_content("id_token=") encoded_id_token = response.location[/(?<=id_token=)[^&]+/] - decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, + OpenidConnect::IdTokenConfig.public_key expect(decoded_token.nonce).to eq("4130930983") expect(decoded_token.exp).to be > Time.now.utc.to_i end @@ -115,7 +118,6 @@ describe OpenidConnect::AuthorizationsController, type: :controller do end describe "#create" do - context "when id_token token" do before do get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token token", @@ -129,14 +131,16 @@ describe OpenidConnect::AuthorizationsController, type: :controller do it "should return the id token in a fragment" do encoded_id_token = response.location[/(?<=id_token=)[^&]+/] - decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, + OpenidConnect::IdTokenConfig.public_key expect(decoded_token.nonce).to eq("4180930983") expect(decoded_token.exp).to be > Time.now.utc.to_i end it "should return a valid access token in a fragment" do encoded_id_token = response.location[/(?<=id_token=)[^&]+/] - decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, + OpenidConnect::IdTokenConfig.public_key access_token = response.location[/(?<=access_token=)[^&]+/] access_token_check_num = UrlSafeBase64.encode64(OpenSSL::Digest::SHA256.digest(access_token)[0, 128 / 8]) expect(decoded_token.at_hash).to eq(access_token_check_num) @@ -158,7 +162,8 @@ describe OpenidConnect::AuthorizationsController, type: :controller do it "should return the id token in a fragment" do expect(response.location).to have_content("id_token=") encoded_id_token = response.location[/(?<=id_token=)[^&]+/] - decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, + OpenidConnect::IdTokenConfig.public_key expect(decoded_token.nonce).to eq("4180930983") expect(decoded_token.exp).to be > Time.now.utc.to_i end @@ -182,6 +187,5 @@ describe OpenidConnect::AuthorizationsController, type: :controller do end end end - end end diff --git a/spec/controllers/openid_connect/id_tokens_controller_spec.rb b/spec/controllers/openid_connect/id_tokens_controller_spec.rb index 6739a3561..1e9e3548b 100644 --- a/spec/controllers/openid_connect/id_tokens_controller_spec.rb +++ b/spec/controllers/openid_connect/id_tokens_controller_spec.rb @@ -9,7 +9,7 @@ describe OpenidConnect::IdTokensController, type: :controller do it "should contain a public key that matches the internal private key" do json = JSON.parse(response.body).with_indifferent_access jwks = JSON::JWK::Set.new json[:keys] - public_keys = jwks.collect do |jwk| + public_keys = jwks.map do |jwk| JSON::JWK.decode jwk end public_key = public_keys.first From b173283692b3ecfdc6f8bccdbfa2f326081da2ec Mon Sep 17 00:00:00 2001 From: augier Date: Mon, 20 Jul 2015 20:05:49 +0200 Subject: [PATCH 025/105] Test for refresh token flow --- app/models/openid_connect/authorization.rb | 9 ++- app/models/openid_connect/id_token.rb | 16 ++-- lib/openid_connect/token_endpoint.rb | 2 +- .../protected_resource_endpoint_spec.rb | 2 +- .../lib/openid_connect/token_endpoint_spec.rb | 76 +++++++++++++++++-- 5 files changed, 89 insertions(+), 16 deletions(-) diff --git a/app/models/openid_connect/authorization.rb b/app/models/openid_connect/authorization.rb index abbe8c090..341dbd67f 100644 --- a/app/models/openid_connect/authorization.rb +++ b/app/models/openid_connect/authorization.rb @@ -10,7 +10,9 @@ class OpenidConnect::Authorization < ActiveRecord::Base has_many :o_auth_access_tokens, dependent: :destroy has_many :id_tokens, dependent: :destroy - def generate_refresh_token + before_validation :setup, on: :create + + def setup self.refresh_token = SecureRandom.hex(32) end @@ -28,5 +30,10 @@ class OpenidConnect::Authorization < ActiveRecord::Base find_by(o_auth_application: app, user: user) end + def self.find_by_refresh_token(client_id, refresh_token) + OpenidConnect::Authorization.joins(:o_auth_application).where( + o_auth_applications: {client_id: client_id}, refresh_token: refresh_token).first + end + # TODO: Consider splitting into subclasses by flow type end diff --git a/app/models/openid_connect/id_token.rb b/app/models/openid_connect/id_token.rb index cd8c4d9c3..fe2cd252e 100644 --- a/app/models/openid_connect/id_token.rb +++ b/app/models/openid_connect/id_token.rb @@ -14,9 +14,17 @@ class OpenidConnect::IdToken < ActiveRecord::Base end def to_response_object(options={}) - claims = { + id_token = OpenIDConnect::ResponseObject::IdToken.new(claims) + id_token.code = options[:code] if options[:code] + id_token.access_token = options[:access_token] if options[:access_token] + id_token + end + + def claims + @claims ||= { iss: AppConfig.environment.url, - sub: AppConfig.environment.url + authorization.o_auth_application.client_id.to_s + authorization.user.id.to_s, # TODO: Convert to proper PPID + # TODO: Convert to proper PPID + sub: "#{AppConfig.environment.url}#{authorization.o_auth_application.client_id}#{authorization.user.id}", aud: authorization.o_auth_application.client_id, exp: expires_at.to_i, iat: created_at.to_i, @@ -24,10 +32,6 @@ class OpenidConnect::IdToken < ActiveRecord::Base nonce: nonce, acr: 0 # TODO: Adjust ? } - id_token = OpenIDConnect::ResponseObject::IdToken.new(claims) - id_token.code = options[:code] if options[:code] - id_token.access_token = options[:access_token] if options[:access_token] - id_token end # TODO: Add support for request objects diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb index 116a5c67b..fd843110a 100644 --- a/lib/openid_connect/token_endpoint.rb +++ b/lib/openid_connect/token_endpoint.rb @@ -40,7 +40,7 @@ module OpenidConnect end def handle_refresh_flow(req, res) - auth = OpenidConnect::Authorization.where(client_id: req.client_id).where(refresh_token: req.refresh_token).first + auth = OpenidConnect::Authorization.find_by_refresh_token req.client_id, req.refresh_token if auth res.access_token = auth.create_access_token else diff --git a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb index 2ceab0972..4eb242b05 100644 --- a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb @@ -5,7 +5,7 @@ describe OpenidConnect::ProtectedResourceEndpoint, type: :request do let!(:client) do OpenidConnect::OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) end - let!(:auth) { OpenidConnect::Authorization.find_or_create(client.client_id, bob) } + let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) } let!(:access_token) { auth.create_access_token.to_s } let!(:invalid_token) { SecureRandom.hex(32).to_s } # TODO: Add tests for expired access tokens diff --git a/spec/lib/openid_connect/token_endpoint_spec.rb b/spec/lib/openid_connect/token_endpoint_spec.rb index 1b60704b5..c5c6af389 100644 --- a/spec/lib/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/openid_connect/token_endpoint_spec.rb @@ -2,62 +2,79 @@ require "spec_helper" describe OpenidConnect::TokenEndpoint, type: :request do let!(:client) { OpenidConnect::OAuthApplication.create!(redirect_uris: ["http://localhost"]) } + let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) } + describe "the password grant type" do context "when the username field is missing" do it "should return an invalid request error" do post "/openid_connect/access_tokens", grant_type: "password", password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret - expect(response.body).to include("'username' required") + expect(response.body).to include "'username' required" end end + context "when the password field is missing" do it "should return an invalid request error" do post "/openid_connect/access_tokens", grant_type: "password", username: "bob", client_id: client.client_id, client_secret: client.client_secret - expect(response.body).to include("'password' required") + expect(response.body).to include "'password' required" end end + context "when the username does not match an existing user" do it "should return an invalid request error" do post "/openid_connect/access_tokens", grant_type: "password", username: "randomnoexist", password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret - expect(response.body).to include("invalid_grant") + expect(response.body).to include "invalid_grant" end end + context "when the password is invalid" do it "should return an invalid request error" do post "/openid_connect/access_tokens", grant_type: "password", username: "bob", password: "wrongpassword", client_id: client.client_id, client_secret: client.client_secret - expect(response.body).to include("invalid_grant") + expect(response.body).to include "invalid_grant" end end + + context "when the client_secret doesn't match" do + it "should return an invalid client error" do + post "/openid_connect/access_tokens", grant_type: "password", username: "bob", + password: "bluepin7", client_id: client.client_id, client_secret: "client.client_secret" + expect(response.body).to include "invalid_client" + end + end + context "when the request is valid" do it "should return an access token" do post "/openid_connect/access_tokens", grant_type: "password", username: "bob", password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret json = JSON.parse(response.body) + expect(json.keys).to include "expires_in" expect(json["access_token"].length).to eq(64) expect(json["token_type"]).to eq("bearer") - expect(json.keys).to include("expires_in") end end + context "when there are duplicate fields" do it "should return an invalid request error" do post "/openid_connect/access_tokens", grant_type: "password", username: "bob", password: "bluepin7", username: "bob", password: "bluepin6", client_id: client.client_id, client_secret: client.client_secret - expect(response.body).to include("invalid_grant") + expect(response.body).to include "invalid_grant" end end + context "when the client is unregistered" do it "should return an error" do post "/openid_connect/access_tokens", grant_type: "password", username: "bob", password: "bluepin7", client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret - expect(response.body).to include("invalid_client") + expect(response.body).to include "invalid_client" end end # TODO: Support a way to prevent brute force attacks using rate-limitation # as specified by RFC 6749 4.3.2 Access Token Request end + describe "an unsupported grant type" do it "should return an unsupported grant type error" do post "/openid_connect/access_tokens", grant_type: "noexistgrant", username: "bob", @@ -65,4 +82,49 @@ describe OpenidConnect::TokenEndpoint, type: :request do expect(response.body).to include "unsupported_grant_type" end end + + describe "the refresh token flow" do + context "when the refresh token is valid" do + it "should return an access token" do + post "/openid_connect/access_tokens", grant_type: "refresh_token", + client_id: client.client_id, client_secret: client.client_secret, refresh_token: auth.refresh_token + json = JSON.parse(response.body) + expect(response.body).to include "expires_in" + expect(json["access_token"].length).to eq(64) + expect(json["token_type"]).to eq("bearer") + end + end + + context "when the refresh token is not valid" do + it "should return an invalid grant error" do + post "/openid_connect/access_tokens", grant_type: "refresh_token", + client_id: client.client_id, client_secret: client.client_secret, refresh_token: " " + expect(response.body).to include "invalid_grant" + end + end + + context "when the client is unregistered" do + it "should return an error" do + post "/openid_connect/access_tokens", grant_type: "refresh_token", refresh_token: auth.refresh_token, + client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret + expect(response.body).to include "invalid_client" + end + end + + context "when the refresh_token field is missing" do + it "should return an invalid request error" do + post "/openid_connect/access_tokens", grant_type: "refresh_token", + client_id: client.client_id, client_secret: client.client_secret + expect(response.body).to include "'refresh_token' required" + end + end + + context "when the client_secret doesn't match" do + it "should return an invalid client error" do + post "/openid_connect/access_tokens", grant_type: "refresh_token", refresh_token: auth.refresh_token, + client_id: client.client_id, client_secret: "client.client_secret" + expect(response.body).to include "invalid_client" + end + end + end end From 979adca1e74fb25396f9bfbbbf05c50ced863aa7 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sat, 25 Jul 2015 15:44:34 +0900 Subject: [PATCH 026/105] Fix account deleter specs Squashed commits: [7ff4276] Adjust discovery controller --- app/controllers/openid_connect/discovery_controller.rb | 4 ++-- app/models/user.rb | 1 - db/schema.rb | 2 +- lib/account_deleter.rb | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/controllers/openid_connect/discovery_controller.rb b/app/controllers/openid_connect/discovery_controller.rb index a11207b8d..8ed066e06 100644 --- a/app/controllers/openid_connect/discovery_controller.rb +++ b/app/controllers/openid_connect/discovery_controller.rb @@ -18,8 +18,8 @@ class OpenidConnect::DiscoveryController < ApplicationController token_endpoint: openid_connect_access_tokens_url, userinfo_endpoint: api_v0_user_url, jwks_uri: File.join(root_url, "openid_connect", "jwks.json"), - scopes_supported: Scope.pluck(:name), - response_types_supported: OAuthApplication.available_response_types, + scopes_supported: OpenidConnect::Scope.pluck(:name), + response_types_supported: OpenidConnect::OAuthApplication.available_response_types, request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), subject_types_supported: %w(public pairwise), id_token_signing_alg_values_supported: %i(RS256), diff --git a/app/models/user.rb b/app/models/user.rb index c77a75e1c..9c4927161 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -78,7 +78,6 @@ class User < ActiveRecord::Base has_many :authorizations, class_name: "OpenidConnect::Authorization" has_many :o_auth_applications, through: :authorizations, class_name: "OpenidConnect::OAuthApplication" - has_many :o_auth_access_tokens, through: :authorizations, class_name: "OpenidConnect::OAuthAccessToken" before_save :guard_unconfirmed_email, :save_person! diff --git a/db/schema.rb b/db/schema.rb index 0b6547dbc..679036156 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150714055110) do +ActiveRecord::Schema.define(version: 20150724152052) do create_table "account_deletions", force: :cascade do |t| t.string "diaspora_handle", limit: 255 diff --git a/lib/account_deleter.rb b/lib/account_deleter.rb index c102cb6c0..10dee342e 100644 --- a/lib/account_deleter.rb +++ b/lib/account_deleter.rb @@ -47,7 +47,7 @@ class AccountDeleter #user deletions def normal_ar_user_associates_to_delete %i(tag_followings invitations_to_me services aspects user_preferences - notifications blocks authorizations o_auth_applications o_auth_access_tokens id_tokens) + notifications blocks authorizations o_auth_applications) end def special_ar_user_associations From de4f68c289f2004b021bd1d50f4a49f4b017ce25 Mon Sep 17 00:00:00 2001 From: augier Date: Sat, 25 Jul 2015 12:01:57 +0200 Subject: [PATCH 027/105] Support for more metadata --- .../authorizations_controller.rb | 7 +++-- .../openid_connect/clients_controller.rb | 2 +- .../openid_connect/o_auth_application.rb | 29 ++++++++++++++++++- .../authorizations/new.html.haml | 2 +- ...150613202109_create_o_auth_applications.rb | 10 ++++++- db/schema.rb | 22 +++++++++----- .../step_definitions/password_flow_steps.rb | 2 +- .../authorizations_controller_spec.rb | 5 ++-- .../openid_connect/clients_controller_spec.rb | 18 ++++++++++-- .../protected_resource_endpoint_spec.rb | 3 +- .../lib/openid_connect/token_endpoint_spec.rb | 5 +++- 11 files changed, 85 insertions(+), 20 deletions(-) diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index 0cf7faeb4..3ef6896a2 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -53,7 +53,8 @@ class OpenidConnect::AuthorizationsController < ApplicationController end def process_authorization_consent(approvedString) - endpoint = OpenidConnect::AuthorizationPoint::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString)) + endpoint = OpenidConnect::AuthorizationPoint::EndpointConfirmationPoint.new( + current_user, to_boolean(approvedString)) handle_confirmation_endpoint_response(endpoint) end @@ -80,7 +81,9 @@ class OpenidConnect::AuthorizationsController < ApplicationController req = Rack::Request.new(request.env) req.update_param("client_id", session[:client_id]) req.update_param("redirect_uri", session[:redirect_uri]) - req.update_param("response_type", session[:response_type].respond_to?(:map) ? session[:response_type].map(&:to_s).join(" ") : session[:response_type]) + req.update_param("response_type", session[:response_type].respond_to?(:map) ? + session[:response_type].map(&:to_s).join(" ") : + session[:response_type]) req.update_param("scopes", session[:scopes]) req.update_param("request_object", session[:request_object]) req.update_param("nonce", session[:nonce]) diff --git a/app/controllers/openid_connect/clients_controller.rb b/app/controllers/openid_connect/clients_controller.rb index a00b9d3d8..accbbb7a7 100644 --- a/app/controllers/openid_connect/clients_controller.rb +++ b/app/controllers/openid_connect/clients_controller.rb @@ -3,7 +3,7 @@ class OpenidConnect::ClientsController < ApplicationController http_error_page_as_json(e) end - rescue_from OpenIDConnect::ValidationFailed do |e| + rescue_from OpenIDConnect::ValidationFailed, ActiveRecord::RecordInvalid do |e| validation_fail_as_json(e) end diff --git a/app/models/openid_connect/o_auth_application.rb b/app/models/openid_connect/o_auth_application.rb index 482f0ad24..f94e4fae2 100644 --- a/app/models/openid_connect/o_auth_application.rb +++ b/app/models/openid_connect/o_auth_application.rb @@ -4,14 +4,26 @@ class OpenidConnect::OAuthApplication < ActiveRecord::Base validates :client_id, presence: true, uniqueness: true validates :client_secret, presence: true + validates :client_name, presence: true serialize :redirect_uris, JSON + serialize :response_types, JSON + serialize :grant_types, JSON + serialize :contacts, JSON before_validation :setup, on: :create def setup self.client_id = SecureRandom.hex(16) self.client_secret = SecureRandom.hex(32) + self.response_types = [] + self.grant_types = [] + self.application_type = "web" + self.contacts = [] + self.logo_uri = "" + self.client_uri = "" + self.policy_uri = "" + self.tos_uri = "" end class << self @@ -24,8 +36,23 @@ class OpenidConnect::OAuthApplication < ActiveRecord::Base build_client_application(registrar) end + private + def build_client_application(registrar) - create! redirect_uris: registrar.redirect_uris + create! registrar_attributes(registrar) + end + + def supported_metadata + %i(client_name response_types grant_types application_type + contacts logo_uri client_uri policy_uri tos_uri) + end + + def registrar_attributes(registrar) + supported_metadata.each_with_object({}) do |key, attr| + if registrar.public_send(key) + attr[key] = registrar.public_send(key) + end + end end end end diff --git a/app/views/openid_connect/authorizations/new.html.haml b/app/views/openid_connect/authorizations/new.html.haml index c134be8a7..9e3b91b8a 100644 --- a/app/views/openid_connect/authorizations/new.html.haml +++ b/app/views/openid_connect/authorizations/new.html.haml @@ -1,4 +1,4 @@ -%h2= @o_auth_application.name +%h2= @o_auth_application.client_name %p= t(".will_be_redirected") = @redirect_uri = t(".with_id_token") diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index 0e36a7006..e9452a6c7 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -4,8 +4,16 @@ class CreateOAuthApplications < ActiveRecord::Migration t.belongs_to :user, index: true t.string :client_id t.string :client_secret - t.string :name + t.string :client_name t.string :redirect_uris + t.string :response_types + t.string :grant_types + t.string :application_type + t.string :contacts + t.string :logo_uri + t.string :client_uri + t.string :policy_uri + t.string :tos_uri t.timestamps null: false end diff --git a/db/schema.rb b/db/schema.rb index 679036156..e2845d469 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -276,13 +276,21 @@ ActiveRecord::Schema.define(version: 20150724152052) do add_index "o_auth_access_tokens", ["authorization_id"], name: "index_o_auth_access_tokens_on_authorization_id", using: :btree create_table "o_auth_applications", force: :cascade do |t| - t.integer "user_id", limit: 4 - t.string "client_id", limit: 255 - t.string "client_secret", limit: 255 - t.string "name", limit: 255 - t.string "redirect_uris", limit: 255 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "user_id", limit: 4 + t.string "client_id", limit: 255 + t.string "client_secret", limit: 255 + t.string "client_name", limit: 255 + t.string "redirect_uris", limit: 255 + t.string "response_types", limit: 255 + t.string "grant_types", limit: 255 + t.string "application_type", limit: 255 + t.string "contacts", limit: 255 + t.string "logo_uri", limit: 255 + t.string "client_uri", limit: 255 + t.string "policy_uri", limit: 255 + t.string "tos_uri", limit: 255 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "o_auth_applications", ["user_id"], name: "index_o_auth_applications_on_user_id", using: :btree diff --git a/features/step_definitions/password_flow_steps.rb b/features/step_definitions/password_flow_steps.rb index 2dcbf7b3f..66c35f2e1 100644 --- a/features/step_definitions/password_flow_steps.rb +++ b/features/step_definitions/password_flow_steps.rb @@ -1,6 +1,6 @@ When /^I register a new client$/ do client_registration_url = "/openid_connect/clients" - post client_registration_url, redirect_uris: ["http://localhost:3000"] + post client_registration_url, redirect_uris: ["http://localhost:3000"], client_name: "diaspora client" end Given /^I send a post request from that client to the password flow token endpoint using "([^\"]*)"'s credentials$/ do |username| diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb index 706bff247..e19b80e21 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -2,11 +2,12 @@ require "spec_helper" describe OpenidConnect::AuthorizationsController, type: :controller do let!(:client) do - OpenidConnect::OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) + OpenidConnect::OAuthApplication.create!( + client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) end let!(:client_with_multiple_redirects) do OpenidConnect::OAuthApplication.create!( - name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/", "http://localhost/"]) + client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/", "http://localhost/"]) end # TODO: jhass - "Might want to setup some factories in spec/factories.rb, see factory_girl's docs." diff --git a/spec/controllers/openid_connect/clients_controller_spec.rb b/spec/controllers/openid_connect/clients_controller_spec.rb index 1f718e408..2bd3cbca4 100644 --- a/spec/controllers/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/openid_connect/clients_controller_spec.rb @@ -4,14 +4,28 @@ describe OpenidConnect::ClientsController, type: :controller do describe "#create" do context "when valid parameters are passed" do it "should return a client id" do - post :create, redirect_uris: ["http://localhost"] + post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", + response_types: [], grant_types: [], application_type: "web", contacts: [], + logo_uri: "http://test.com/logo.png", client_uri: "http://test.com/client", + policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos" client_json = JSON.parse(response.body) expect(client_json["o_auth_application"]["client_id"].length).to eq(32) end end context "when redirect uri is missing" do it "should return a invalid_client_metadata error" do - post :create + post :create, response_types: [], grant_types: [], application_type: "web", contacts: [], + logo_uri: "http://test.com/logo.png", client_uri: "http://test.com/client", + policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos" + client_json = JSON.parse(response.body) + expect(client_json["error"]).to have_content("invalid_client_metadata") + end + end + context "when redirect client_name is missing" do + it "should return a invalid_client_metadata error" do + post :create, redirect_uris: ["http://localhost"], response_types: [], grant_types: [], + application_type: "web", contacts: [], logo_uri: "http://test.com/logo.png", + client_uri: "http://test.com/client", policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos" client_json = JSON.parse(response.body) expect(client_json["error"]).to have_content("invalid_client_metadata") end diff --git a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb index 4eb242b05..4addeb470 100644 --- a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb @@ -3,7 +3,8 @@ require "spec_helper" describe OpenidConnect::ProtectedResourceEndpoint, type: :request do describe "getting the user info" do let!(:client) do - OpenidConnect::OAuthApplication.create!(name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) + OpenidConnect::OAuthApplication.create!( + client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) end let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) } let!(:access_token) { auth.create_access_token.to_s } diff --git a/spec/lib/openid_connect/token_endpoint_spec.rb b/spec/lib/openid_connect/token_endpoint_spec.rb index c5c6af389..736fb715d 100644 --- a/spec/lib/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/openid_connect/token_endpoint_spec.rb @@ -1,7 +1,10 @@ require "spec_helper" describe OpenidConnect::TokenEndpoint, type: :request do - let!(:client) { OpenidConnect::OAuthApplication.create!(redirect_uris: ["http://localhost"]) } + let!(:client) do + OpenidConnect::OAuthApplication.create!( + redirect_uris: ["http://localhost"], client_name: "diaspora client") + end let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) } describe "the password grant type" do From 3cbe75469b480235db19dd8bac0a2b99369f370d Mon Sep 17 00:00:00 2001 From: theworldbright Date: Mon, 27 Jul 2015 18:26:41 +0900 Subject: [PATCH 028/105] Add support for scopes Remove scopes from tokens Squashed commits: [83db38f] Add redirect uris to supported metadata --- app/controllers/api/v0/base_controller.rb | 5 +- app/controllers/api/v0/users_controller.rb | 10 +- .../authorizations_controller.rb | 4 +- app/models/openid_connect/authorization.rb | 7 + .../openid_connect/o_auth_access_token.rb | 5 - .../openid_connect/o_auth_application.rb | 2 +- app/models/openid_connect/scope.rb | 3 +- app/models/openid_connect/scopes_tokens.rb | 7 - ..._create_authorization_scopes_join_table.rb | 8 ++ ...create_authorizations_scopes_join_table.rb | 8 -- ...8155747_create_scopes_tokens_join_table.rb | 8 -- db/schema.rb | 24 ++-- features/desktop/oauth_password_flow.feature | 1 + features/desktop/oidc_implicit_flow.feature | 2 +- .../step_definitions/implicit_flow_steps.rb | 17 +-- .../step_definitions/password_flow_steps.rb | 26 ++-- .../authorization_point/endpoint.rb | 4 +- .../endpoint_confirmation_point.rb | 1 + .../protected_resource_endpoint.rb | 12 +- lib/openid_connect/token_endpoint.rb | 8 ++ .../protected_resource_endpoint_spec.rb | 120 ++++++++++-------- .../lib/openid_connect/token_endpoint_spec.rb | 54 ++++---- 22 files changed, 165 insertions(+), 171 deletions(-) delete mode 100644 app/models/openid_connect/scopes_tokens.rb create mode 100644 db/migrate/20150708155202_create_authorization_scopes_join_table.rb delete mode 100644 db/migrate/20150708155202_create_authorizations_scopes_join_table.rb delete mode 100644 db/migrate/20150708155747_create_scopes_tokens_join_table.rb diff --git a/app/controllers/api/v0/base_controller.rb b/app/controllers/api/v0/base_controller.rb index 85a3913d1..15b85151e 100644 --- a/app/controllers/api/v0/base_controller.rb +++ b/app/controllers/api/v0/base_controller.rb @@ -1,6 +1,7 @@ class Api::V0::BaseController < ApplicationController include OpenidConnect::ProtectedResourceEndpoint - before_filter :require_access_token - + def user + current_token ? current_token.authorization.user : nil + end end diff --git a/app/controllers/api/v0/users_controller.rb b/app/controllers/api/v0/users_controller.rb index 70e04f372..11f68ffe9 100644 --- a/app/controllers/api/v0/users_controller.rb +++ b/app/controllers/api/v0/users_controller.rb @@ -1,11 +1,9 @@ class Api::V0::UsersController < Api::V0::BaseController + before_filter do + require_access_token OpenidConnect::Scope.find_by(name: "read") + end + def show render json: user end - - private - - def user - current_token.authorization.user - end end diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index 3ef6896a2..d1e3604dd 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -47,7 +47,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController session[:client_id] = @o_auth_application.client_id session[:response_type] = @response_type session[:redirect_uri] = @redirect_uri - session[:scopes] = @scopes.map(&:name) + session[:scopes] = @scopes.map(&:name).join(" ") session[:request_object] = @request_object session[:nonce] = params[:nonce] end @@ -84,7 +84,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController req.update_param("response_type", session[:response_type].respond_to?(:map) ? session[:response_type].map(&:to_s).join(" ") : session[:response_type]) - req.update_param("scopes", session[:scopes]) + req.update_param("scope", session[:scopes]) req.update_param("request_object", session[:request_object]) req.update_param("nonce", session[:nonce]) end diff --git a/app/models/openid_connect/authorization.rb b/app/models/openid_connect/authorization.rb index 341dbd67f..2d9b0f951 100644 --- a/app/models/openid_connect/authorization.rb +++ b/app/models/openid_connect/authorization.rb @@ -6,6 +6,7 @@ class OpenidConnect::Authorization < ActiveRecord::Base validates :o_auth_application, presence: true validates :user, uniqueness: {scope: :o_auth_application} + has_many :authorization_scopes has_many :scopes, through: :authorization_scopes has_many :o_auth_access_tokens, dependent: :destroy has_many :id_tokens, dependent: :destroy @@ -16,6 +17,12 @@ class OpenidConnect::Authorization < ActiveRecord::Base self.refresh_token = SecureRandom.hex(32) end + def accessible?(required_scopes=nil) + Array(required_scopes).all? do |required_scope| + scopes.include? required_scope + end + end + def create_access_token o_auth_access_tokens.create!.bearer_token # TODO: Add support for request object diff --git a/app/models/openid_connect/o_auth_access_token.rb b/app/models/openid_connect/o_auth_access_token.rb index cc72dc3d9..6374e968e 100644 --- a/app/models/openid_connect/o_auth_access_token.rb +++ b/app/models/openid_connect/o_auth_access_token.rb @@ -1,6 +1,5 @@ class OpenidConnect::OAuthAccessToken < ActiveRecord::Base belongs_to :authorization - has_many :scopes, through: :scope_tokens before_validation :setup, on: :create @@ -20,8 +19,4 @@ class OpenidConnect::OAuthAccessToken < ActiveRecord::Base expires_in: (expires_at - Time.now.utc).to_i ) end - - def accessible?(_scopes_or_claims_=nil) - true # TODO: For now don't support scopes - end end diff --git a/app/models/openid_connect/o_auth_application.rb b/app/models/openid_connect/o_auth_application.rb index f94e4fae2..c4c1f0aa3 100644 --- a/app/models/openid_connect/o_auth_application.rb +++ b/app/models/openid_connect/o_auth_application.rb @@ -44,7 +44,7 @@ class OpenidConnect::OAuthApplication < ActiveRecord::Base def supported_metadata %i(client_name response_types grant_types application_type - contacts logo_uri client_uri policy_uri tos_uri) + contacts logo_uri client_uri policy_uri tos_uri redirect_uris) end def registrar_attributes(registrar) diff --git a/app/models/openid_connect/scope.rb b/app/models/openid_connect/scope.rb index e5449b648..eff4da110 100644 --- a/app/models/openid_connect/scope.rb +++ b/app/models/openid_connect/scope.rb @@ -1,8 +1,7 @@ class OpenidConnect::Scope < ActiveRecord::Base - has_many :o_auth_access_token, through: :scope_tokens has_many :authorizations, through: :authorization_scopes validates :name, presence: true, uniqueness: true - # TODO: Incomplete class + # TODO: Add constants so scopes can be referenced as OpenidConnect::Scope::Read end diff --git a/app/models/openid_connect/scopes_tokens.rb b/app/models/openid_connect/scopes_tokens.rb deleted file mode 100644 index 3c336ac73..000000000 --- a/app/models/openid_connect/scopes_tokens.rb +++ /dev/null @@ -1,7 +0,0 @@ -class OpenidConnect::ScopeToken < ActiveRecord::Base - belongs_to :scope - belongs_to :o_auth_access_token - - validates :scope, presence: true - validates :token, presence: true -end diff --git a/db/migrate/20150708155202_create_authorization_scopes_join_table.rb b/db/migrate/20150708155202_create_authorization_scopes_join_table.rb new file mode 100644 index 000000000..79c275b41 --- /dev/null +++ b/db/migrate/20150708155202_create_authorization_scopes_join_table.rb @@ -0,0 +1,8 @@ +class CreateAuthorizationScopesJoinTable < ActiveRecord::Migration + def change + create_table :authorization_scopes, id: false do |t| + t.belongs_to :authorization, index: true + t.belongs_to :scope, index: true + end + end +end diff --git a/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb b/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb deleted file mode 100644 index c91e50d11..000000000 --- a/db/migrate/20150708155202_create_authorizations_scopes_join_table.rb +++ /dev/null @@ -1,8 +0,0 @@ -class CreateAuthorizationsScopesJoinTable < ActiveRecord::Migration - def change - create_table :authorizations_scopes, id: false do |t| - t.belongs_to :authorization, index: true - t.belongs_to :scope, index: true - end - end -end diff --git a/db/migrate/20150708155747_create_scopes_tokens_join_table.rb b/db/migrate/20150708155747_create_scopes_tokens_join_table.rb deleted file mode 100644 index a52835048..000000000 --- a/db/migrate/20150708155747_create_scopes_tokens_join_table.rb +++ /dev/null @@ -1,8 +0,0 @@ -class CreateScopesTokensJoinTable < ActiveRecord::Migration - def change - create_table :scopes_tokens, id: false do |t| - t.belongs_to :scope, index: true - t.belongs_to :o_auth_access_token, index: true - end - end -end diff --git a/db/schema.rb b/db/schema.rb index e2845d469..dfaf0ed3b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -55,6 +55,14 @@ ActiveRecord::Schema.define(version: 20150724152052) do add_index "aspects", ["user_id", "contacts_visible"], name: "index_aspects_on_user_id_and_contacts_visible", using: :btree add_index "aspects", ["user_id"], name: "index_aspects_on_user_id", using: :btree + create_table "authorization_scopes", id: false, force: :cascade do |t| + t.integer "authorization_id", limit: 4 + t.integer "scope_id", limit: 4 + end + + add_index "authorization_scopes", ["authorization_id"], name: "index_authorization_scopes_on_authorization_id", using: :btree + add_index "authorization_scopes", ["scope_id"], name: "index_authorization_scopes_on_scope_id", using: :btree + create_table "authorizations", force: :cascade do |t| t.integer "user_id", limit: 4 t.integer "o_auth_application_id", limit: 4 @@ -66,14 +74,6 @@ ActiveRecord::Schema.define(version: 20150724152052) do add_index "authorizations", ["o_auth_application_id"], name: "index_authorizations_on_o_auth_application_id", using: :btree add_index "authorizations", ["user_id"], name: "index_authorizations_on_user_id", using: :btree - create_table "authorizations_scopes", id: false, force: :cascade do |t| - t.integer "authorization_id", limit: 4 - t.integer "scope_id", limit: 4 - end - - add_index "authorizations_scopes", ["authorization_id"], name: "index_authorizations_scopes_on_authorization_id", using: :btree - add_index "authorizations_scopes", ["scope_id"], name: "index_authorizations_scopes_on_scope_id", using: :btree - create_table "blocks", force: :cascade do |t| t.integer "user_id", limit: 4 t.integer "person_id", limit: 4 @@ -523,14 +523,6 @@ ActiveRecord::Schema.define(version: 20150724152052) do t.datetime "updated_at", null: false end - create_table "scopes_tokens", id: false, force: :cascade do |t| - t.integer "scope_id", limit: 4 - t.integer "o_auth_access_token_id", limit: 4 - end - - add_index "scopes_tokens", ["o_auth_access_token_id"], name: "index_scopes_tokens_on_o_auth_access_token_id", using: :btree - add_index "scopes_tokens", ["scope_id"], name: "index_scopes_tokens_on_scope_id", using: :btree - create_table "services", force: :cascade do |t| t.string "type", limit: 127, null: false t.integer "user_id", limit: 4, null: false diff --git a/features/desktop/oauth_password_flow.feature b/features/desktop/oauth_password_flow.feature index 7fa59c4fd..4bde1b08d 100644 --- a/features/desktop/oauth_password_flow.feature +++ b/features/desktop/oauth_password_flow.feature @@ -1,6 +1,7 @@ Feature: Access protected resources using password flow Background: Given a user with username "kent" + And all scopes exist Scenario: Invalid credentials to token endpoint When I register a new client diff --git a/features/desktop/oidc_implicit_flow.feature b/features/desktop/oidc_implicit_flow.feature index 92bf59d5a..480b0a928 100644 --- a/features/desktop/oidc_implicit_flow.feature +++ b/features/desktop/oidc_implicit_flow.feature @@ -2,7 +2,7 @@ Feature: Access protected resources using implicit flow Background: Given a user with username "kent" - And the OpenID scope exists + And all scopes exist Scenario: Invalid client id to auth endpoint When I register a new client diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb index cc0f63e34..7c50f3159 100644 --- a/features/step_definitions/implicit_flow_steps.rb +++ b/features/step_definitions/implicit_flow_steps.rb @@ -1,24 +1,18 @@ o_auth_query_params = %i( redirect_uri=http://localhost:3000 - response_type=id_token token - scope=openid + response_type=id_token%20token + scope=openid%20read nonce=hello state=hi ).join("&") -Given(/^the OpenID scope exists$/) do - OpenidConnect::Scope.create(name: "openid") -end - Given /^I send a post request from that client to the implicit flow authorization endpoint$/ do client_json = JSON.parse(last_response.body) - auth_endpoint_url = "/openid_connect/authorizations/new" - visit "#{auth_endpoint_url}?client_id=#{client_json["o_auth_application"]["client_id"]}&#{o_auth_query_params}" + visit new_openid_connect_authorization_path + "?client_id=#{client_json["o_auth_application"]["client_id"]}&#{o_auth_query_params}" end Given /^I send a post request from that client to the implicit flow authorization endpoint using a invalid client id/ do - auth_endpoint_url = "/openid_connect/authorizations/new" - visit "#{auth_endpoint_url}?client_id=randomid&#{o_auth_query_params}" + visit new_openid_connect_authorization_path + "?client_id=randomid&#{o_auth_query_params}" end When /^I give my consent and authorize the client$/ do @@ -38,8 +32,7 @@ end When /^I parse the bearer tokens and use it to access user info$/ do access_token = current_url[/(?<=access_token=)[^&]+/] - user_info_endpoint_url = "/api/v0/user/" - get user_info_endpoint_url, access_token: access_token + get api_v0_user_path, access_token: access_token end Then /^I should see an "([^\"]*)" error$/ do |error_message| diff --git a/features/step_definitions/password_flow_steps.rb b/features/step_definitions/password_flow_steps.rb index 66c35f2e1..778399a60 100644 --- a/features/step_definitions/password_flow_steps.rb +++ b/features/step_definitions/password_flow_steps.rb @@ -1,35 +1,37 @@ +Given(/^all scopes exist$/) do + OpenidConnect::Scope.find_or_create_by(name: "openid") + OpenidConnect::Scope.find_or_create_by(name: "read") +end + When /^I register a new client$/ do - client_registration_url = "/openid_connect/clients" - post client_registration_url, redirect_uris: ["http://localhost:3000"], client_name: "diaspora client" + post openid_connect_clients_path, redirect_uris: ["http://localhost:3000"], client_name: "diaspora client" end Given /^I send a post request from that client to the password flow token endpoint using "([^\"]*)"'s credentials$/ do |username| client_json = JSON.parse(last_response.body) user = User.find_by(username: username) - token_endpoint_url = "/openid_connect/access_tokens" - post token_endpoint_url, grant_type: "password", username: user.username, + post openid_connect_access_tokens_path, grant_type: "password", username: user.username, password: "password", # Password has been hard coded as all test accounts seem to have a password of "password" client_id: client_json["o_auth_application"]["client_id"], - client_secret: client_json["o_auth_application"]["client_secret"] + client_secret: client_json["o_auth_application"]["client_secret"], + scope: "read" end Given /^I send a post request from that client to the password flow token endpoint using invalid credentials$/ do client_json = JSON.parse(last_response.body) - token_endpoint_url = "/openid_connect/access_tokens" - post token_endpoint_url, grant_type: "password", username: "bob", password: "wrongpassword", + post openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "wrongpassword", client_id: client_json["o_auth_application"]["client_id"], - client_secret: client_json["o_auth_application"]["client_secret"] + client_secret: client_json["o_auth_application"]["client_secret"], + scope: "read" end When /^I use received valid bearer tokens to access user info$/ do access_token_json = JSON.parse(last_response.body) - user_info_endpoint_url = "/api/v0/user/" - get user_info_endpoint_url, access_token: access_token_json["access_token"] + get api_v0_user_path, access_token: access_token_json["access_token"] end When /^I use invalid bearer tokens to access user info$/ do - user_info_endpoint_url = "/api/v0/user/" - get user_info_endpoint_url, access_token: SecureRandom.hex(32) + get api_v0_user_path, access_token: SecureRandom.hex(32) end Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username| diff --git a/lib/openid_connect/authorization_point/endpoint.rb b/lib/openid_connect/authorization_point/endpoint.rb index 839bed9c7..a65974273 100644 --- a/lib/openid_connect/authorization_point/endpoint.rb +++ b/lib/openid_connect/authorization_point/endpoint.rb @@ -45,8 +45,8 @@ module OpenidConnect end def build_scopes(req) - @scopes = req.scope.map {|scope| - OpenidConnect::Scope.where(name: scope).first.tap do |scope| + @scopes = req.scope.map {|scope_name| + OpenidConnect::Scope.where(name: scope_name).first.tap do |scope| req.invalid_scope! "Unknown scope: #{scope}" unless scope end } diff --git a/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb index cdb4d9474..7a6b83183 100644 --- a/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -21,6 +21,7 @@ module OpenidConnect # TODO: Add support for request object and auth code def approved!(req, res) auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: @o_auth_application, user: @user) + auth.scopes << @scopes response_types = Array(req.response_type) if response_types.include?(:token) res.access_token = auth.create_access_token diff --git a/lib/openid_connect/protected_resource_endpoint.rb b/lib/openid_connect/protected_resource_endpoint.rb index 8d5a37165..abc606981 100644 --- a/lib/openid_connect/protected_resource_endpoint.rb +++ b/lib/openid_connect/protected_resource_endpoint.rb @@ -2,20 +2,14 @@ module OpenidConnect module ProtectedResourceEndpoint attr_reader :current_token - def require_access_token + def require_access_token(*required_scopes) @current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN] - unless @current_token + unless @current_token && @current_token.authorization raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user") end - # TODO: This block is useless until we actually start checking for scopes - unless @current_token.try(:accessible?, required_scopes) + unless @current_token.authorization.try(:accessible?, required_scopes) raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope) end end - - # TODO: Scopes should be implemented here - def required_scopes - nil - end end end diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb index fd843110a..53347eba2 100644 --- a/lib/openid_connect/token_endpoint.rb +++ b/lib/openid_connect/token_endpoint.rb @@ -29,7 +29,13 @@ module OpenidConnect user = User.find_for_database_authentication(username: req.username) if user if user.valid_password?(req.password) + scope_list = req.scope.map { |scope_name| + OpenidConnect::Scope.find_by(name: scope_name).tap do |scope| + req.invalid_scope! "Unknown scope: #{scope}" unless scope + end + } # TODO: Check client scope permissions auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: o_auth_app, user: user) + auth.scopes << scope_list res.access_token = auth.create_access_token else req.invalid_grant! @@ -40,6 +46,8 @@ module OpenidConnect end def handle_refresh_flow(req, res) + # Handle as if scope request was omitted even if provided. + # See https://tools.ietf.org/html/rfc6749#section-6 for handling auth = OpenidConnect::Authorization.find_by_refresh_token req.client_id, req.refresh_token if auth res.access_token = auth.create_access_token diff --git a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb index 4addeb470..7fd948a75 100644 --- a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb @@ -1,75 +1,89 @@ require "spec_helper" describe OpenidConnect::ProtectedResourceEndpoint, type: :request do - describe "getting the user info" do - let!(:client) do - OpenidConnect::OAuthApplication.create!( - client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) - end - let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) } - let!(:access_token) { auth.create_access_token.to_s } - let!(:invalid_token) { SecureRandom.hex(32).to_s } - # TODO: Add tests for expired access tokens + let!(:client) do + OpenidConnect::OAuthApplication.create!( + client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) + end + let(:auth_with_read) do + auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) + auth.scopes << [OpenidConnect::Scope.find_or_create_by(name: "read")] + auth + end + let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } + let(:auth_with_read_and_write) do + auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) + auth.scopes << [OpenidConnect::Scope.find_or_create_by(name: "read"), OpenidConnect::Scope.find_or_create_by(name: "write")] + auth + end + let!(:access_token_with_read_and_write) { auth_with_read_and_write.create_access_token.to_s } + let(:invalid_token) { SecureRandom.hex(32).to_s } - context "when access token is valid" do - it "shows the user's username and email" do - get "/api/v0/user/", access_token: access_token + # TODO: Add tests for expired access tokens + + context "when read scope access token is provided for read required endpoint" do + describe "user info endpoint" do + before do + get api_v0_user_path, access_token: access_token_with_read + end + + it "shows the info" do json_body = JSON.parse(response.body) expect(json_body["username"]).to eq(bob.username) expect(json_body["email"]).to eq(bob.email) end - it "should include private in the cache-control header" do - get "/api/v0/user/", access_token: access_token + + it "includes private in the cache-control header" do expect(response.headers["Cache-Control"]).to include("private") end end + end - context "when no access token is provided" do - it "should respond with a 401 Unauthorized response" do - get "/api/v0/user/" - expect(response.status).to be(401) - end - it "should have an auth-scheme value of Bearer" do - get "/api/v0/user/" - expect(response.headers["WWW-Authenticate"]).to include("Bearer") - end + context "when no access token is provided" do + it "should respond with a 401 Unauthorized response" do + get api_v0_user_path + expect(response.status).to be(401) + end + it "should have an auth-scheme value of Bearer" do + get api_v0_user_path + expect(response.headers["WWW-Authenticate"]).to include("Bearer") + end + end + + context "when an invalid access token is provided" do + before do + get api_v0_user_path, access_token: invalid_token end - context "when an invalid access token is provided" do - before do - get "/api/v0/user/", access_token: invalid_token - end - - it "should respond with a 401 Unauthorized response" do - expect(response.status).to be(401) - end - - it "should have an auth-scheme value of Bearer" do - expect(response.headers["WWW-Authenticate"]).to include("Bearer") - end - - it "should contain an invalid_token error" do - expect(response.body).to include("invalid_token") - end + it "should respond with a 401 Unauthorized response" do + expect(response.status).to be(401) end - context "when authorization has been destroyed" do - before do - auth.destroy - get "/api/v0/user/", access_token: access_token - end + it "should have an auth-scheme value of Bearer" do + expect(response.headers["WWW-Authenticate"]).to include("Bearer") + end - it "should respond with a 401 Unauthorized response" do - expect(response.status).to be(401) - end + it "should contain an invalid_token error" do + expect(response.body).to include("invalid_token") + end + end - it "should have an auth-scheme value of Bearer" do - expect(response.headers["WWW-Authenticate"]).to include("Bearer") - end + context "when authorization has been destroyed" do + before do + auth_with_read.destroy + get api_v0_user_path, access_token: access_token_with_read + end - it "should contain an invalid_token error" do - expect(response.body).to include("invalid_token") - end + it "should respond with a 401 Unauthorized response" do + expect(response.status).to be(401) + end + + it "should have an auth-scheme value of Bearer" do + expect(response.headers["WWW-Authenticate"]).to include("Bearer") + end + + it "should contain an invalid_token error" do + expect(response.body).to include("invalid_token") end end end diff --git a/spec/lib/openid_connect/token_endpoint_spec.rb b/spec/lib/openid_connect/token_endpoint_spec.rb index 736fb715d..7af40fd21 100644 --- a/spec/lib/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/openid_connect/token_endpoint_spec.rb @@ -5,53 +5,57 @@ describe OpenidConnect::TokenEndpoint, type: :request do OpenidConnect::OAuthApplication.create!( redirect_uris: ["http://localhost"], client_name: "diaspora client") end - let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) } + let(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) } + + before do + OpenidConnect::Scope.find_or_create_by(name: "read") + end describe "the password grant type" do context "when the username field is missing" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", grant_type: "password", password: "bluepin7", - client_id: client.client_id, client_secret: client.client_secret + post openid_connect_access_tokens_path, grant_type: "password", password: "bluepin7", + client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "'username' required" end end context "when the password field is missing" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", grant_type: "password", username: "bob", - client_id: client.client_id, client_secret: client.client_secret + post openid_connect_access_tokens_path, grant_type: "password", username: "bob", + client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "'password' required" end end context "when the username does not match an existing user" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", grant_type: "password", username: "randomnoexist", - password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret + post openid_connect_access_tokens_path, grant_type: "password", username: "randomnoexist", + password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "invalid_grant" end end context "when the password is invalid" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", grant_type: "password", username: "bob", - password: "wrongpassword", client_id: client.client_id, client_secret: client.client_secret + post openid_connect_access_tokens_path, grant_type: "password", username: "bob", + password: "wrongpassword", client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "invalid_grant" end end context "when the client_secret doesn't match" do it "should return an invalid client error" do - post "/openid_connect/access_tokens", grant_type: "password", username: "bob", - password: "bluepin7", client_id: client.client_id, client_secret: "client.client_secret" + post openid_connect_access_tokens_path, grant_type: "password", username: "bob", + password: "bluepin7", client_id: client.client_id, client_secret: "client.client_secret", scope: "read" expect(response.body).to include "invalid_client" end end context "when the request is valid" do it "should return an access token" do - post "/openid_connect/access_tokens", grant_type: "password", username: "bob", - password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret + post openid_connect_access_tokens_path, grant_type: "password", username: "bob", + password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read" json = JSON.parse(response.body) expect(json.keys).to include "expires_in" expect(json["access_token"].length).to eq(64) @@ -61,16 +65,16 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when there are duplicate fields" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", grant_type: "password", username: "bob", password: "bluepin7", - username: "bob", password: "bluepin6", client_id: client.client_id, client_secret: client.client_secret + post openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "bluepin7", + username: "bob", password: "bluepin6", client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "invalid_grant" end end context "when the client is unregistered" do it "should return an error" do - post "/openid_connect/access_tokens", grant_type: "password", username: "bob", - password: "bluepin7", client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret + post openid_connect_access_tokens_path, grant_type: "password", username: "bob", + password: "bluepin7", client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret, scope: "read" expect(response.body).to include "invalid_client" end end @@ -80,8 +84,8 @@ describe OpenidConnect::TokenEndpoint, type: :request do describe "an unsupported grant type" do it "should return an unsupported grant type error" do - post "/openid_connect/access_tokens", grant_type: "noexistgrant", username: "bob", - password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret + post openid_connect_access_tokens_path, grant_type: "noexistgrant", username: "bob", + password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "unsupported_grant_type" end end @@ -89,7 +93,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do describe "the refresh token flow" do context "when the refresh token is valid" do it "should return an access token" do - post "/openid_connect/access_tokens", grant_type: "refresh_token", + post openid_connect_access_tokens_path, grant_type: "refresh_token", client_id: client.client_id, client_secret: client.client_secret, refresh_token: auth.refresh_token json = JSON.parse(response.body) expect(response.body).to include "expires_in" @@ -100,15 +104,15 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the refresh token is not valid" do it "should return an invalid grant error" do - post "/openid_connect/access_tokens", grant_type: "refresh_token", - client_id: client.client_id, client_secret: client.client_secret, refresh_token: " " + post openid_connect_access_tokens_path, grant_type: "refresh_token", + client_id: client.client_id, client_secret: client.client_secret, refresh_token: "123456" expect(response.body).to include "invalid_grant" end end context "when the client is unregistered" do it "should return an error" do - post "/openid_connect/access_tokens", grant_type: "refresh_token", refresh_token: auth.refresh_token, + post openid_connect_access_tokens_path, grant_type: "refresh_token", refresh_token: auth.refresh_token, client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret expect(response.body).to include "invalid_client" end @@ -116,7 +120,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the refresh_token field is missing" do it "should return an invalid request error" do - post "/openid_connect/access_tokens", grant_type: "refresh_token", + post openid_connect_access_tokens_path, grant_type: "refresh_token", client_id: client.client_id, client_secret: client.client_secret expect(response.body).to include "'refresh_token' required" end @@ -124,7 +128,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the client_secret doesn't match" do it "should return an invalid client error" do - post "/openid_connect/access_tokens", grant_type: "refresh_token", refresh_token: auth.refresh_token, + post openid_connect_access_tokens_path, grant_type: "refresh_token", refresh_token: auth.refresh_token, client_id: client.client_id, client_secret: "client.client_secret" expect(response.body).to include "invalid_client" end From cd2f1215e8f86bcdd9e6cf78a41c73f93342f927 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 12 Aug 2015 19:51:33 +0900 Subject: [PATCH 029/105] Adjust protect resource endpoint spec --- app/controllers/api/v0/users_controller.rb | 2 +- .../protected_resource_endpoint_spec.rb | 29 +++++-------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/app/controllers/api/v0/users_controller.rb b/app/controllers/api/v0/users_controller.rb index 11f68ffe9..5a96e8d73 100644 --- a/app/controllers/api/v0/users_controller.rb +++ b/app/controllers/api/v0/users_controller.rb @@ -1,5 +1,5 @@ class Api::V0::UsersController < Api::V0::BaseController - before_filter do + before_action do require_access_token OpenidConnect::Scope.find_by(name: "read") end diff --git a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb index 7fd948a75..cd344c3a9 100644 --- a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/openid_connect/protected_resource_endpoint_spec.rb @@ -1,41 +1,28 @@ require "spec_helper" describe OpenidConnect::ProtectedResourceEndpoint, type: :request do + # TODO: Replace with factory let!(:client) do OpenidConnect::OAuthApplication.create!( client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) end let(:auth_with_read) do - auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) + auth = OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) auth.scopes << [OpenidConnect::Scope.find_or_create_by(name: "read")] auth end let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } - let(:auth_with_read_and_write) do - auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) - auth.scopes << [OpenidConnect::Scope.find_or_create_by(name: "read"), OpenidConnect::Scope.find_or_create_by(name: "write")] - auth - end - let!(:access_token_with_read_and_write) { auth_with_read_and_write.create_access_token.to_s } let(:invalid_token) { SecureRandom.hex(32).to_s } # TODO: Add tests for expired access tokens - context "when read scope access token is provided for read required endpoint" do - describe "user info endpoint" do - before do - get api_v0_user_path, access_token: access_token_with_read - end + context "when valid access token is provided" do + before do + get api_v0_user_path, access_token: access_token_with_read + end - it "shows the info" do - json_body = JSON.parse(response.body) - expect(json_body["username"]).to eq(bob.username) - expect(json_body["email"]).to eq(bob.email) - end - - it "includes private in the cache-control header" do - expect(response.headers["Cache-Control"]).to include("private") - end + it "includes private in the cache-control header" do + expect(response.headers["Cache-Control"]).to include("private") end end From bc5e5c742064a223e7377af6ab029f4f2d14f3fe Mon Sep 17 00:00:00 2001 From: theworldbright Date: Thu, 13 Aug 2015 13:21:25 +0900 Subject: [PATCH 030/105] Fix pronto errors --- .../authorizations_controller.rb | 106 ++++++++++++++++++ .../api/openid_connect/clients_controller.rb | 37 ++++++ .../openid_connect/discovery_controller.rb | 34 ++++++ .../openid_connect/id_tokens_controller.rb | 15 +++ app/controllers/api/v0/base_controller.rb | 12 +- app/controllers/api/v0/users_controller.rb | 16 ++- .../authorizations_controller.rb | 91 --------------- .../openid_connect/clients_controller.rb | 33 ------ .../openid_connect/discovery_controller.rb | 30 ----- .../openid_connect/id_tokens_controller.rb | 5 - .../api/openid_connect/authorization.rb | 50 +++++++++ .../api/openid_connect/authorization_scope.rb | 11 ++ app/models/api/openid_connect/id_token.rb | 42 +++++++ .../api/openid_connect/o_auth_access_token.rb | 26 +++++ .../api/openid_connect/o_auth_application.rb | 60 ++++++++++ app/models/api/openid_connect/scope.rb | 11 ++ app/models/openid_connect/authorization.rb | 46 -------- .../openid_connect/authorization_scope.rb | 7 -- app/models/openid_connect/id_token.rb | 38 ------- .../openid_connect/o_auth_access_token.rb | 22 ---- .../openid_connect/o_auth_application.rb | 58 ---------- app/models/openid_connect/scope.rb | 7 -- app/models/user.rb | 4 +- .../openid_connect/authorizations/_form.haml | 2 +- .../authorizations/new.html.haml | 4 +- config/application.rb | 2 +- config/locales/diaspora/en.yml | 19 ++-- config/routes.rb | 31 ++--- .../step_definitions/implicit_flow_steps.rb | 5 +- .../step_definitions/password_flow_steps.rb | 10 +- .../authorization_point/endpoint.rb | 58 ++++++++++ .../endpoint_confirmation_point.rb | 50 +++++++++ .../endpoint_start_point.rb | 13 +++ lib/api/openid_connect/id_token_config.rb | 13 +++ .../protected_resource_endpoint.rb | 15 +++ lib/api/openid_connect/token_endpoint.rb | 73 ++++++++++++ .../authorization_point/endpoint.rb | 58 ---------- .../endpoint_confirmation_point.rb | 38 ------- .../endpoint_start_point.rb | 11 -- lib/openid_connect/id_token_config.rb | 11 -- .../protected_resource_endpoint.rb | 15 --- lib/openid_connect/token_endpoint.rb | 67 ----------- .../authorizations_controller_spec.rb | 24 ++-- .../openid_connect/clients_controller_spec.rb | 2 +- .../discovery_controller_spec.rb | 4 +- .../id_tokens_controller_spec.rb | 4 +- spec/integration/api/users_controller_spec.rb | 27 +++++ .../protected_resource_endpoint_spec.rb | 14 ++- .../openid_connect/token_endpoint_spec.rb | 42 +++---- 49 files changed, 746 insertions(+), 627 deletions(-) create mode 100644 app/controllers/api/openid_connect/authorizations_controller.rb create mode 100644 app/controllers/api/openid_connect/clients_controller.rb create mode 100644 app/controllers/api/openid_connect/discovery_controller.rb create mode 100644 app/controllers/api/openid_connect/id_tokens_controller.rb delete mode 100644 app/controllers/openid_connect/authorizations_controller.rb delete mode 100644 app/controllers/openid_connect/clients_controller.rb delete mode 100644 app/controllers/openid_connect/discovery_controller.rb delete mode 100644 app/controllers/openid_connect/id_tokens_controller.rb create mode 100644 app/models/api/openid_connect/authorization.rb create mode 100644 app/models/api/openid_connect/authorization_scope.rb create mode 100644 app/models/api/openid_connect/id_token.rb create mode 100644 app/models/api/openid_connect/o_auth_access_token.rb create mode 100644 app/models/api/openid_connect/o_auth_application.rb create mode 100644 app/models/api/openid_connect/scope.rb delete mode 100644 app/models/openid_connect/authorization.rb delete mode 100644 app/models/openid_connect/authorization_scope.rb delete mode 100644 app/models/openid_connect/id_token.rb delete mode 100644 app/models/openid_connect/o_auth_access_token.rb delete mode 100644 app/models/openid_connect/o_auth_application.rb delete mode 100644 app/models/openid_connect/scope.rb rename app/views/{ => api}/openid_connect/authorizations/_form.haml (71%) rename app/views/{ => api}/openid_connect/authorizations/new.html.haml (67%) create mode 100644 lib/api/openid_connect/authorization_point/endpoint.rb create mode 100644 lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb create mode 100644 lib/api/openid_connect/authorization_point/endpoint_start_point.rb create mode 100644 lib/api/openid_connect/id_token_config.rb create mode 100644 lib/api/openid_connect/protected_resource_endpoint.rb create mode 100644 lib/api/openid_connect/token_endpoint.rb delete mode 100644 lib/openid_connect/authorization_point/endpoint.rb delete mode 100644 lib/openid_connect/authorization_point/endpoint_confirmation_point.rb delete mode 100644 lib/openid_connect/authorization_point/endpoint_start_point.rb delete mode 100644 lib/openid_connect/id_token_config.rb delete mode 100644 lib/openid_connect/protected_resource_endpoint.rb delete mode 100644 lib/openid_connect/token_endpoint.rb rename spec/controllers/{ => api}/openid_connect/authorizations_controller_spec.rb (91%) rename spec/controllers/{ => api}/openid_connect/clients_controller_spec.rb (96%) rename spec/controllers/{ => api}/openid_connect/discovery_controller_spec.rb (87%) rename spec/controllers/{ => api}/openid_connect/id_tokens_controller_spec.rb (70%) create mode 100644 spec/integration/api/users_controller_spec.rb rename spec/lib/{ => api}/openid_connect/protected_resource_endpoint_spec.rb (86%) rename spec/lib/{ => api}/openid_connect/token_endpoint_spec.rb (71%) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb new file mode 100644 index 000000000..16509f555 --- /dev/null +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -0,0 +1,106 @@ +module Api + module OpenidConnect + class AuthorizationsController < ApplicationController + rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| + logger.info e.backtrace[0, 10].join("\n") + render json: {error: e.message || :error, status: e.status} + end + + before_action :authenticate_user! + + def new + request_authorization_consent_form + end + + def create + restore_request_parameters + process_authorization_consent(params[:approve]) + end + + private + + def request_authorization_consent_form # TODO: Add support for prompt params + if Api::OpenidConnect::Authorization.find_by_client_id_and_user(params[:client_id], current_user) + process_authorization_consent("true") + else + endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user) + handle_start_point_response(endpoint) + end + end + + def handle_start_point_response(endpoint) + _status, header, response = *endpoint.call(request.env) + if response.redirect? + redirect_to header["Location"] + else + save_params_and_render_consent_form(endpoint) + end + end + + def save_params_and_render_consent_form(endpoint) + @o_auth_application, @response_type, @redirect_uri, @scopes, @request_object = *[ + endpoint.o_auth_application, endpoint.response_type, + endpoint.redirect_uri, endpoint.scopes, endpoint.request_object + ] + save_request_parameters + render :new + end + + def save_request_parameters + session[:client_id] = @o_auth_application.client_id + session[:response_type] = @response_type + session[:redirect_uri] = @redirect_uri + session[:scopes] = scopes_as_space_seperated_values + session[:request_object] = @request_object + session[:nonce] = params[:nonce] + end + + def scopes_as_space_seperated_values + @scopes.map(&:name).join(" ") + end + + def process_authorization_consent(approvedString) + endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointConfirmationPoint.new( + current_user, to_boolean(approvedString)) + handle_confirmation_endpoint_response(endpoint) + end + + def handle_confirmation_endpoint_response(endpoint) + _status, header, _response = *endpoint.call(request.env) + delete_authorization_session_variables + redirect_to header["Location"] + end + + def delete_authorization_session_variables + session.delete(:client_id) + session.delete(:response_type) + session.delete(:redirect_uri) + session.delete(:scopes) + session.delete(:request_object) + session.delete(:nonce) + end + + def to_boolean(str) + str.downcase == "true" + end + + def restore_request_parameters + req = Rack::Request.new(request.env) + req.update_param("client_id", session[:client_id]) + req.update_param("redirect_uri", session[:redirect_uri]) + req.update_param("response_type", response_type_as_space_seperated_values) + req.update_param("scope", session[:scopes]) + req.update_param("request_object", session[:request_object]) + req.update_param("nonce", session[:nonce]) + end + + def response_type_as_space_seperated_values + if session[:response_type].respond_to?(:map) + session[:response_type].map(&:to_s).join(" ") + else + session[:response_type] + end + end + end + end +end diff --git a/app/controllers/api/openid_connect/clients_controller.rb b/app/controllers/api/openid_connect/clients_controller.rb new file mode 100644 index 000000000..d122a1692 --- /dev/null +++ b/app/controllers/api/openid_connect/clients_controller.rb @@ -0,0 +1,37 @@ +module Api + module OpenidConnect + class ClientsController < ApplicationController + rescue_from OpenIDConnect::HttpError do |e| + http_error_page_as_json(e) + end + + rescue_from OpenIDConnect::ValidationFailed, ActiveRecord::RecordInvalid do |e| + validation_fail_as_json(e) + end + + def create + registrar = OpenIDConnect::Client::Registrar.new(request.url, params) + client = Api::OpenidConnect::OAuthApplication.register! registrar + render json: client + end + + private + + def http_error_page_as_json(e) + render json: + { + error: :invalid_request, + error_description: e.message + }, status: 400 + end + + def validation_fail_as_json(e) + render json: + { + error: :invalid_client_metadata, + error_description: e.message + }, status: 400 + end + end + end +end diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb new file mode 100644 index 000000000..4d4b56bb5 --- /dev/null +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -0,0 +1,34 @@ +module Api + module OpenidConnect + class DiscoveryController < ApplicationController + def webfinger + jrd = { + links: [{ + rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, + href: File.join(root_url, "api", "openid_connect") + }] + } + jrd[:subject] = params[:resource] if params[:resource].present? + render json: jrd, content_type: "application/jrd+json" + end + + def configuration + render json: OpenIDConnect::Discovery::Provider::Config::Response.new( + issuer: root_url, + registration_endpoint: api_openid_connect_clients_url, + authorization_endpoint: new_api_openid_connect_authorization_url, + token_endpoint: api_openid_connect_access_tokens_url, + userinfo_endpoint: api_v0_user_url, + jwks_uri: File.join(root_url, "api", "openid_connect", "jwks.json"), + scopes_supported: Api::OpenidConnect::Scope.pluck(:name), + response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types, + request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), + subject_types_supported: %w(public pairwise), + id_token_signing_alg_values_supported: %i(RS256), + token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post) + # TODO: claims_supported: ["sub", "iss", "name", "email"] + ) + end + end + end +end diff --git a/app/controllers/api/openid_connect/id_tokens_controller.rb b/app/controllers/api/openid_connect/id_tokens_controller.rb new file mode 100644 index 000000000..dae760892 --- /dev/null +++ b/app/controllers/api/openid_connect/id_tokens_controller.rb @@ -0,0 +1,15 @@ +module Api + module OpenidConnect + class IdTokensController < ApplicationController + def jwks + render json: JSON::JWK::Set.new(build_jwk).as_json + end + + private + + def build_jwk + JSON::JWK.new(Api::OpenidConnect::IdTokenConfig.public_key, use: :sig) + end + end + end +end diff --git a/app/controllers/api/v0/base_controller.rb b/app/controllers/api/v0/base_controller.rb index 15b85151e..edcd178ed 100644 --- a/app/controllers/api/v0/base_controller.rb +++ b/app/controllers/api/v0/base_controller.rb @@ -1,7 +1,11 @@ -class Api::V0::BaseController < ApplicationController - include OpenidConnect::ProtectedResourceEndpoint +module Api + module V0 + class BaseController < ApplicationController + include Api::OpenidConnect::ProtectedResourceEndpoint - def user - current_token ? current_token.authorization.user : nil + def current_user + current_token ? current_token.authorization.user : nil + end + end end end diff --git a/app/controllers/api/v0/users_controller.rb b/app/controllers/api/v0/users_controller.rb index 5a96e8d73..d2cdf9dfb 100644 --- a/app/controllers/api/v0/users_controller.rb +++ b/app/controllers/api/v0/users_controller.rb @@ -1,9 +1,13 @@ -class Api::V0::UsersController < Api::V0::BaseController - before_action do - require_access_token OpenidConnect::Scope.find_by(name: "read") - end +module Api + module V0 + class UsersController < Api::V0::BaseController + before_action do + require_access_token Api::OpenidConnect::Scope.find_by(name: "read") + end - def show - render json: user + def show + render json: current_user + end + end end end diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb deleted file mode 100644 index d1e3604dd..000000000 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ /dev/null @@ -1,91 +0,0 @@ -class OpenidConnect::AuthorizationsController < ApplicationController - rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| - logger.info e.backtrace[0, 10].join("\n") - render json: {error: e.message || :error, status: e.status} - end - - before_action :authenticate_user! - - def new - request_authorization_consent_form - end - - def create - restore_request_parameters - process_authorization_consent(params[:approve]) - end - - private - - def request_authorization_consent_form # TODO: Add support for prompt params - if OpenidConnect::Authorization.find_by_client_id_and_user(params[:client_id], current_user) - process_authorization_consent("true") - else - endpoint = OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user) - handle_start_point_response(endpoint) - end - end - - def handle_start_point_response(endpoint) - _status, header, response = *endpoint.call(request.env) - if response.redirect? - redirect_to header["Location"] - else - save_params_and_render_consent_form(endpoint) - end - end - - def save_params_and_render_consent_form(endpoint) - @o_auth_application, @response_type, @redirect_uri, @scopes, @request_object = *[ - endpoint.o_auth_application, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint.request_object - ] - save_request_parameters - render :new - end - - def save_request_parameters - session[:client_id] = @o_auth_application.client_id - session[:response_type] = @response_type - session[:redirect_uri] = @redirect_uri - session[:scopes] = @scopes.map(&:name).join(" ") - session[:request_object] = @request_object - session[:nonce] = params[:nonce] - end - - def process_authorization_consent(approvedString) - endpoint = OpenidConnect::AuthorizationPoint::EndpointConfirmationPoint.new( - current_user, to_boolean(approvedString)) - handle_confirmation_endpoint_response(endpoint) - end - - def handle_confirmation_endpoint_response(endpoint) - _status, header, _response = *endpoint.call(request.env) - delete_authorization_session_variables - redirect_to header["Location"] - end - - def delete_authorization_session_variables - session.delete(:client_id) - session.delete(:response_type) - session.delete(:redirect_uri) - session.delete(:scopes) - session.delete(:request_object) - session.delete(:nonce) - end - - def to_boolean(str) - str.downcase == "true" - end - - def restore_request_parameters - req = Rack::Request.new(request.env) - req.update_param("client_id", session[:client_id]) - req.update_param("redirect_uri", session[:redirect_uri]) - req.update_param("response_type", session[:response_type].respond_to?(:map) ? - session[:response_type].map(&:to_s).join(" ") : - session[:response_type]) - req.update_param("scope", session[:scopes]) - req.update_param("request_object", session[:request_object]) - req.update_param("nonce", session[:nonce]) - end -end diff --git a/app/controllers/openid_connect/clients_controller.rb b/app/controllers/openid_connect/clients_controller.rb deleted file mode 100644 index accbbb7a7..000000000 --- a/app/controllers/openid_connect/clients_controller.rb +++ /dev/null @@ -1,33 +0,0 @@ -class OpenidConnect::ClientsController < ApplicationController - rescue_from OpenIDConnect::HttpError do |e| - http_error_page_as_json(e) - end - - rescue_from OpenIDConnect::ValidationFailed, ActiveRecord::RecordInvalid do |e| - validation_fail_as_json(e) - end - - def create - registrar = OpenIDConnect::Client::Registrar.new(request.url, params) - client = OpenidConnect::OAuthApplication.register! registrar - render json: client - end - - private - - def http_error_page_as_json(e) - render json: - { - error: :invalid_request, - error_description: e.message - }, status: 400 - end - - def validation_fail_as_json(e) - render json: - { - error: :invalid_client_metadata, - error_description: e.message - }, status: 400 - end -end diff --git a/app/controllers/openid_connect/discovery_controller.rb b/app/controllers/openid_connect/discovery_controller.rb deleted file mode 100644 index 8ed066e06..000000000 --- a/app/controllers/openid_connect/discovery_controller.rb +++ /dev/null @@ -1,30 +0,0 @@ -class OpenidConnect::DiscoveryController < ApplicationController - def webfinger - jrd = { - links: [{ - rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, - href: File.join(root_url, "openid_connect") - }] - } - jrd[:subject] = params[:resource] if params[:resource].present? - render json: jrd, content_type: "application/jrd+json" - end - - def configuration - render json: OpenIDConnect::Discovery::Provider::Config::Response.new( - issuer: root_url, - registration_endpoint: openid_connect_clients_url, - authorization_endpoint: new_openid_connect_authorization_url, - token_endpoint: openid_connect_access_tokens_url, - userinfo_endpoint: api_v0_user_url, - jwks_uri: File.join(root_url, "openid_connect", "jwks.json"), - scopes_supported: OpenidConnect::Scope.pluck(:name), - response_types_supported: OpenidConnect::OAuthApplication.available_response_types, - request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), - subject_types_supported: %w(public pairwise), - id_token_signing_alg_values_supported: %i(RS256), - token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), - # TODO: claims_supported: ["sub", "iss", "name", "email"] - ) - end -end diff --git a/app/controllers/openid_connect/id_tokens_controller.rb b/app/controllers/openid_connect/id_tokens_controller.rb deleted file mode 100644 index 75e0d69b4..000000000 --- a/app/controllers/openid_connect/id_tokens_controller.rb +++ /dev/null @@ -1,5 +0,0 @@ -class OpenidConnect::IdTokensController < ApplicationController - def jwks - render json: JSON::JWK::Set.new(JSON::JWK.new(OpenidConnect::IdTokenConfig.public_key, use: :sig)).as_json - end -end diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb new file mode 100644 index 000000000..88a511fbe --- /dev/null +++ b/app/models/api/openid_connect/authorization.rb @@ -0,0 +1,50 @@ +module Api + module OpenidConnect + class Authorization < ActiveRecord::Base + belongs_to :user + belongs_to :o_auth_application + + validates :user, presence: true + validates :o_auth_application, presence: true + validates :user, uniqueness: {scope: :o_auth_application} + + has_many :authorization_scopes + has_many :scopes, through: :authorization_scopes + has_many :o_auth_access_tokens, dependent: :destroy + has_many :id_tokens, dependent: :destroy + + before_validation :setup, on: :create + + def setup + self.refresh_token = SecureRandom.hex(32) + end + + def accessible?(required_scopes=nil) + Array(required_scopes).all? do |required_scope| + scopes.include? required_scope + end + end + + def create_access_token + o_auth_access_tokens.create!.bearer_token + # TODO: Add support for request object + end + + def create_id_token(nonce) + id_tokens.create!(nonce: nonce) + end + + def self.find_by_client_id_and_user(client_id, user) + app = Api::OpenidConnect::OAuthApplication.find_by(client_id: client_id) + find_by(o_auth_application: app, user: user) + end + + def self.find_by_refresh_token(client_id, refresh_token) + Api::OpenidConnect::Authorization.joins(:o_auth_application).find_by( + o_auth_applications: {client_id: client_id}, refresh_token: refresh_token) + end + + # TODO: Consider splitting into subclasses by flow type + end + end +end diff --git a/app/models/api/openid_connect/authorization_scope.rb b/app/models/api/openid_connect/authorization_scope.rb new file mode 100644 index 000000000..fce15b225 --- /dev/null +++ b/app/models/api/openid_connect/authorization_scope.rb @@ -0,0 +1,11 @@ +module Api + module OpenidConnect + class AuthorizationScope < ActiveRecord::Base + belongs_to :authorization + belongs_to :scope + + validates :authorization, presence: true + validates :scope, presence: true + end + end +end diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb new file mode 100644 index 000000000..15becc1f8 --- /dev/null +++ b/app/models/api/openid_connect/id_token.rb @@ -0,0 +1,42 @@ +module Api + module OpenidConnect + class IdToken < ActiveRecord::Base + belongs_to :authorization + + before_validation :setup, on: :create + + default_scope { where("expires_at >= ?", Time.zone.now.utc) } + + def setup + self.expires_at = 30.minutes.from_now + end + + def to_jwt(options={}) + to_response_object(options).to_jwt OpenidConnect::IdTokenConfig.private_key + end + + def to_response_object(options={}) + id_token = OpenIDConnect::ResponseObject::IdToken.new(claims) + id_token.code = options[:code] if options[:code] + id_token.access_token = options[:access_token] if options[:access_token] + id_token + end + + def claims + @claims ||= { + iss: AppConfig.environment.url, + # TODO: Convert to proper PPID + sub: "#{AppConfig.environment.url}#{authorization.o_auth_application.client_id}#{authorization.user.id}", + aud: authorization.o_auth_application.client_id, + exp: expires_at.to_i, + iat: created_at.to_i, + auth_time: authorization.user.current_sign_in_at.to_i, + nonce: nonce, + acr: 0 # TODO: Adjust ? + } + end + + # TODO: Add support for request objects + end + end +end diff --git a/app/models/api/openid_connect/o_auth_access_token.rb b/app/models/api/openid_connect/o_auth_access_token.rb new file mode 100644 index 000000000..a62b72ad9 --- /dev/null +++ b/app/models/api/openid_connect/o_auth_access_token.rb @@ -0,0 +1,26 @@ +module Api + module OpenidConnect + class OAuthAccessToken < ActiveRecord::Base + belongs_to :authorization + + before_validation :setup, on: :create + + validates :token, presence: true, uniqueness: true + validates :authorization, presence: true + + scope :valid, ->(time) { where("expires_at >= ?", time) } + + def setup + self.token = SecureRandom.hex(32) + self.expires_at = 24.hours.from_now + end + + def bearer_token + @bearer_token ||= Rack::OAuth2::AccessToken::Bearer.new( + access_token: token, + expires_in: (expires_at - Time.zone.now.utc).to_i + ) + end + end + end +end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb new file mode 100644 index 000000000..2a97665a2 --- /dev/null +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -0,0 +1,60 @@ +module Api + module OpenidConnect + class OAuthApplication < ActiveRecord::Base + has_many :authorizations + has_many :user, through: :authorizations + + validates :client_id, presence: true, uniqueness: true + validates :client_secret, presence: true + validates :client_name, presence: true + + serialize :redirect_uris, JSON + serialize :response_types, JSON + serialize :grant_types, JSON + serialize :contacts, JSON + + before_validation :setup, on: :create + + def setup + self.client_id = SecureRandom.hex(16) + self.client_secret = SecureRandom.hex(32) + self.response_types = [] + self.grant_types = [] + self.application_type = "web" + self.contacts = [] + self.logo_uri = "" + self.client_uri = "" + self.policy_uri = "" + self.tos_uri = "" + end + + class << self + def available_response_types + ["id_token", "id_token token"] + end + + def register!(registrar) + registrar.validate! + build_client_application(registrar) + end + + private + + def build_client_application(registrar) + create! registrar_attributes(registrar) + end + + def supported_metadata + %i(client_name response_types grant_types application_type + contacts logo_uri client_uri policy_uri tos_uri redirect_uris) + end + + def registrar_attributes(registrar) + supported_metadata.each_with_object({}) do |key, attr| + attr[key] = registrar.public_send(key) if registrar.public_send(key) + end + end + end + end + end +end diff --git a/app/models/api/openid_connect/scope.rb b/app/models/api/openid_connect/scope.rb new file mode 100644 index 000000000..7b7d66ac1 --- /dev/null +++ b/app/models/api/openid_connect/scope.rb @@ -0,0 +1,11 @@ +module Api + module OpenidConnect + class Scope < ActiveRecord::Base + has_many :authorizations, through: :authorization_scopes + + validates :name, presence: true, uniqueness: true + + # TODO: Add constants so scopes can be referenced as OpenidConnect::Scope::Read + end + end +end diff --git a/app/models/openid_connect/authorization.rb b/app/models/openid_connect/authorization.rb deleted file mode 100644 index 2d9b0f951..000000000 --- a/app/models/openid_connect/authorization.rb +++ /dev/null @@ -1,46 +0,0 @@ -class OpenidConnect::Authorization < ActiveRecord::Base - belongs_to :user - belongs_to :o_auth_application - - validates :user, presence: true - validates :o_auth_application, presence: true - validates :user, uniqueness: {scope: :o_auth_application} - - has_many :authorization_scopes - has_many :scopes, through: :authorization_scopes - has_many :o_auth_access_tokens, dependent: :destroy - has_many :id_tokens, dependent: :destroy - - before_validation :setup, on: :create - - def setup - self.refresh_token = SecureRandom.hex(32) - end - - def accessible?(required_scopes=nil) - Array(required_scopes).all? do |required_scope| - scopes.include? required_scope - end - end - - def create_access_token - o_auth_access_tokens.create!.bearer_token - # TODO: Add support for request object - end - - def create_id_token(nonce) - id_tokens.create!(nonce: nonce) - end - - def self.find_by_client_id_and_user(client_id, user) - app = OpenidConnect::OAuthApplication.find_by(client_id: client_id) - find_by(o_auth_application: app, user: user) - end - - def self.find_by_refresh_token(client_id, refresh_token) - OpenidConnect::Authorization.joins(:o_auth_application).where( - o_auth_applications: {client_id: client_id}, refresh_token: refresh_token).first - end - - # TODO: Consider splitting into subclasses by flow type -end diff --git a/app/models/openid_connect/authorization_scope.rb b/app/models/openid_connect/authorization_scope.rb deleted file mode 100644 index 30e160281..000000000 --- a/app/models/openid_connect/authorization_scope.rb +++ /dev/null @@ -1,7 +0,0 @@ -class OpenidConnect::AuthorizationScope < ActiveRecord::Base - belongs_to :authorization - belongs_to :scope - - validates :authorization, presence: true - validates :scope, presence: true -end diff --git a/app/models/openid_connect/id_token.rb b/app/models/openid_connect/id_token.rb deleted file mode 100644 index fe2cd252e..000000000 --- a/app/models/openid_connect/id_token.rb +++ /dev/null @@ -1,38 +0,0 @@ -class OpenidConnect::IdToken < ActiveRecord::Base - belongs_to :authorization - - before_validation :setup, on: :create - - default_scope -> { where("expires_at >= ?", Time.now.utc) } - - def setup - self.expires_at = 30.minutes.from_now - end - - def to_jwt(options={}) - to_response_object(options).to_jwt OpenidConnect::IdTokenConfig.private_key - end - - def to_response_object(options={}) - id_token = OpenIDConnect::ResponseObject::IdToken.new(claims) - id_token.code = options[:code] if options[:code] - id_token.access_token = options[:access_token] if options[:access_token] - id_token - end - - def claims - @claims ||= { - iss: AppConfig.environment.url, - # TODO: Convert to proper PPID - sub: "#{AppConfig.environment.url}#{authorization.o_auth_application.client_id}#{authorization.user.id}", - aud: authorization.o_auth_application.client_id, - exp: expires_at.to_i, - iat: created_at.to_i, - auth_time: authorization.user.current_sign_in_at.to_i, - nonce: nonce, - acr: 0 # TODO: Adjust ? - } - end - - # TODO: Add support for request objects -end diff --git a/app/models/openid_connect/o_auth_access_token.rb b/app/models/openid_connect/o_auth_access_token.rb deleted file mode 100644 index 6374e968e..000000000 --- a/app/models/openid_connect/o_auth_access_token.rb +++ /dev/null @@ -1,22 +0,0 @@ -class OpenidConnect::OAuthAccessToken < ActiveRecord::Base - belongs_to :authorization - - before_validation :setup, on: :create - - validates :token, presence: true, uniqueness: true - validates :authorization, presence: true - - scope :valid, ->(time) { where("expires_at >= ?", time) } - - def setup - self.token = SecureRandom.hex(32) - self.expires_at = 24.hours.from_now - end - - def bearer_token - @bearer_token ||= Rack::OAuth2::AccessToken::Bearer.new( - access_token: token, - expires_in: (expires_at - Time.now.utc).to_i - ) - end -end diff --git a/app/models/openid_connect/o_auth_application.rb b/app/models/openid_connect/o_auth_application.rb deleted file mode 100644 index c4c1f0aa3..000000000 --- a/app/models/openid_connect/o_auth_application.rb +++ /dev/null @@ -1,58 +0,0 @@ -class OpenidConnect::OAuthApplication < ActiveRecord::Base - has_many :authorizations - has_many :user, through: :authorizations - - validates :client_id, presence: true, uniqueness: true - validates :client_secret, presence: true - validates :client_name, presence: true - - serialize :redirect_uris, JSON - serialize :response_types, JSON - serialize :grant_types, JSON - serialize :contacts, JSON - - before_validation :setup, on: :create - - def setup - self.client_id = SecureRandom.hex(16) - self.client_secret = SecureRandom.hex(32) - self.response_types = [] - self.grant_types = [] - self.application_type = "web" - self.contacts = [] - self.logo_uri = "" - self.client_uri = "" - self.policy_uri = "" - self.tos_uri = "" - end - - class << self - def available_response_types - ["id_token", "id_token token"] - end - - def register!(registrar) - registrar.validate! - build_client_application(registrar) - end - - private - - def build_client_application(registrar) - create! registrar_attributes(registrar) - end - - def supported_metadata - %i(client_name response_types grant_types application_type - contacts logo_uri client_uri policy_uri tos_uri redirect_uris) - end - - def registrar_attributes(registrar) - supported_metadata.each_with_object({}) do |key, attr| - if registrar.public_send(key) - attr[key] = registrar.public_send(key) - end - end - end - end -end diff --git a/app/models/openid_connect/scope.rb b/app/models/openid_connect/scope.rb deleted file mode 100644 index eff4da110..000000000 --- a/app/models/openid_connect/scope.rb +++ /dev/null @@ -1,7 +0,0 @@ -class OpenidConnect::Scope < ActiveRecord::Base - has_many :authorizations, through: :authorization_scopes - - validates :name, presence: true, uniqueness: true - - # TODO: Add constants so scopes can be referenced as OpenidConnect::Scope::Read -end diff --git a/app/models/user.rb b/app/models/user.rb index 9c4927161..a7b58882c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -76,8 +76,8 @@ class User < ActiveRecord::Base has_many :reports - has_many :authorizations, class_name: "OpenidConnect::Authorization" - has_many :o_auth_applications, through: :authorizations, class_name: "OpenidConnect::OAuthApplication" + has_many :authorizations, class_name: "Api::OpenidConnect::Authorization" + has_many :o_auth_applications, through: :authorizations, class_name: "Api::OpenidConnect::OAuthApplication" before_save :guard_unconfirmed_email, :save_person! diff --git a/app/views/openid_connect/authorizations/_form.haml b/app/views/api/openid_connect/authorizations/_form.haml similarity index 71% rename from app/views/openid_connect/authorizations/_form.haml rename to app/views/api/openid_connect/authorizations/_form.haml index 9a72bff90..0e1662d12 100644 --- a/app/views/openid_connect/authorizations/_form.haml +++ b/app/views/api/openid_connect/authorizations/_form.haml @@ -1,4 +1,4 @@ -= form_tag openid_connect_authorizations_path, class: action do += form_tag api_openid_connect_authorizations_path, class: action do - if action == :approve = submit_tag t(".approve") = hidden_field_tag :approve, true diff --git a/app/views/openid_connect/authorizations/new.html.haml b/app/views/api/openid_connect/authorizations/new.html.haml similarity index 67% rename from app/views/openid_connect/authorizations/new.html.haml rename to app/views/api/openid_connect/authorizations/new.html.haml index 9e3b91b8a..8009785ba 100644 --- a/app/views/openid_connect/authorizations/new.html.haml +++ b/app/views/api/openid_connect/authorizations/new.html.haml @@ -10,5 +10,5 @@ %ul %pre= JSON.pretty_generate @request_object.as_json -= render 'openid_connect/authorizations/form', action: :approve -= render 'openid_connect/authorizations/form', action: :deny += render 'api/openid_connect/authorizations/form', action: :approve += render 'api/openid_connect/authorizations/form', action: :deny diff --git a/config/application.rb b/config/application.rb index 7fc97f5ca..e5d10d066 100644 --- a/config/application.rb +++ b/config/application.rb @@ -109,7 +109,7 @@ module Diaspora config.action_mailer.asset_host = AppConfig.pod_uri.to_s config.middleware.use Rack::OAuth2::Server::Resource::Bearer, "OpenID Connect" do |req| - OpenidConnect::OAuthAccessToken.valid(Time.now.utc).find_by(token: req.access_token) || req.invalid_token! + Api::OpenidConnect::OAuthAccessToken.valid(Time.zone.now.utc).find_by(token: req.access_token) || req.invalid_token! end end end diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 113180940..e53b386c3 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -881,15 +881,16 @@ en: Hoping to see you again, The diaspora* email robot! - openid_connect: - authorizations: - new: - will_be_redirected: "You will be redirected to" - with_id_token: "with an id token if approved or an error if denied" - requested_objects: "Request Objects (Currently not supported)" - form: - approve: "Approve" - deny: "Deny" + api: + openid_connect: + authorizations: + new: + will_be_redirected: "You will be redirected to" + with_id_token: "with an id token if approved or an error if denied" + requested_objects: "Request Objects (Currently not supported)" + form: + approve: "Approve" + deny: "Deny" people: zero: "No people" one: "1 person" diff --git a/config/routes.rb b/config/routes.rb index e1c9bdf2e..a9739b73a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -233,22 +233,23 @@ Diaspora::Application.routes.draw do # Startpage root :to => 'home#show' - # OpenID Connect & OAuth - namespace :openid_connect do - resources :clients, only: :create - post "access_tokens", to: proc {|env| OpenidConnect::TokenEndpoint.new.call(env) } - - # Authorization Servers MUST support the use of the HTTP GET and POST methods at the Authorization Endpoint - # See http://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation - resources :authorizations, only: %i(new create) - post "authorizations/new", to: "authorizations#new" - - get ".well-known/webfinger", to: "discovery#webfinger" - get ".well-known/openid-configuration", to: "discovery#configuration" - get "jwks.json", to: "id_tokens#jwks" - end - api_version(module: "Api::V0", path: {value: "api/v0"}, default: true) do match "user", to: "users#show", via: %i(get post) end + + namespace :api do + namespace :openid_connect do + resources :clients, only: :create + post "access_tokens", to: proc {|env| Api::OpenidConnect::TokenEndpoint.new.call(env) } + + # Authorization Servers MUST support the use of the HTTP GET and POST methods at the Authorization Endpoint + # See http://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation + resources :authorizations, only: %i(new create) + post "authorizations/new", to: "authorizations#new" + + get ".well-known/webfinger", to: "discovery#webfinger" + get ".well-known/openid-configuration", to: "discovery#configuration" + get "jwks.json", to: "id_tokens#jwks" + end + end end diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb index 7c50f3159..fad520faa 100644 --- a/features/step_definitions/implicit_flow_steps.rb +++ b/features/step_definitions/implicit_flow_steps.rb @@ -8,11 +8,12 @@ o_auth_query_params = %i( Given /^I send a post request from that client to the implicit flow authorization endpoint$/ do client_json = JSON.parse(last_response.body) - visit new_openid_connect_authorization_path + "?client_id=#{client_json["o_auth_application"]["client_id"]}&#{o_auth_query_params}" + visit new_api_openid_connect_authorization_path + + "?client_id=#{client_json['o_auth_application']['client_id']}&#{o_auth_query_params}" end Given /^I send a post request from that client to the implicit flow authorization endpoint using a invalid client id/ do - visit new_openid_connect_authorization_path + "?client_id=randomid&#{o_auth_query_params}" + visit new_api_openid_connect_authorization_path + "?client_id=randomid&#{o_auth_query_params}" end When /^I give my consent and authorize the client$/ do diff --git a/features/step_definitions/password_flow_steps.rb b/features/step_definitions/password_flow_steps.rb index 778399a60..2a68d1082 100644 --- a/features/step_definitions/password_flow_steps.rb +++ b/features/step_definitions/password_flow_steps.rb @@ -1,16 +1,16 @@ Given(/^all scopes exist$/) do - OpenidConnect::Scope.find_or_create_by(name: "openid") - OpenidConnect::Scope.find_or_create_by(name: "read") + Api::OpenidConnect::Scope.find_or_create_by(name: "openid") + Api::OpenidConnect::Scope.find_or_create_by(name: "read") end When /^I register a new client$/ do - post openid_connect_clients_path, redirect_uris: ["http://localhost:3000"], client_name: "diaspora client" + post api_openid_connect_clients_path, redirect_uris: ["http://localhost:3000"], client_name: "diaspora client" end Given /^I send a post request from that client to the password flow token endpoint using "([^\"]*)"'s credentials$/ do |username| client_json = JSON.parse(last_response.body) user = User.find_by(username: username) - post openid_connect_access_tokens_path, grant_type: "password", username: user.username, + post api_openid_connect_access_tokens_path, grant_type: "password", username: user.username, password: "password", # Password has been hard coded as all test accounts seem to have a password of "password" client_id: client_json["o_auth_application"]["client_id"], client_secret: client_json["o_auth_application"]["client_secret"], @@ -19,7 +19,7 @@ end Given /^I send a post request from that client to the password flow token endpoint using invalid credentials$/ do client_json = JSON.parse(last_response.body) - post openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "wrongpassword", + post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "wrongpassword", client_id: client_json["o_auth_application"]["client_id"], client_secret: client_json["o_auth_application"]["client_secret"], scope: "read" diff --git a/lib/api/openid_connect/authorization_point/endpoint.rb b/lib/api/openid_connect/authorization_point/endpoint.rb new file mode 100644 index 000000000..29d010f91 --- /dev/null +++ b/lib/api/openid_connect/authorization_point/endpoint.rb @@ -0,0 +1,58 @@ +module Api + module OpenidConnect + module AuthorizationPoint + class Endpoint + attr_accessor :app, :user, :o_auth_application, :redirect_uri, :response_type, + :scopes, :_request_, :request_uri, :request_object, :nonce + delegate :call, to: :app + + def initialize(current_user) + @user = current_user + @app = Rack::OAuth2::Server::Authorize.new do |req, res| + build_attributes(req, res) + if OAuthApplication.available_response_types.include? Array(req.response_type).map(&:to_s).join(" ") + handle_response_type(req, res) + else + req.unsupported_response_type! + end + end + end + + def build_attributes(req, res) + build_client(req) + build_redirect_uri(req, res) + verify_nonce(req, res) + build_scopes(req) + end + + def handle_response_type(_req, _res) + # Implemented by subclass + end + + private + + def build_client(req) + @o_auth_application = OAuthApplication.find_by_client_id(req.client_id) || req.bad_request! + end + + def build_redirect_uri(req, res) + res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@o_auth_application.redirect_uris) + end + + def verify_nonce(req, res) + req.invalid_request! "nonce required" if res.protocol_params_location == :fragment && req.nonce.blank? + end + + def build_scopes(req) + @scopes = req.scope.map {|scope_name| + OpenidConnect::Scope.where(name: scope_name).first.tap do |scope| + req.invalid_scope! "Unknown scope: #{scope}" unless scope + end + } + end + + # TODO: buildResponseType(req) + end + end + end +end diff --git a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb new file mode 100644 index 000000000..a500b9dcc --- /dev/null +++ b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -0,0 +1,50 @@ +module Api + module OpenidConnect + module AuthorizationPoint + class EndpointConfirmationPoint < Endpoint + def initialize(current_user, approved=false) + super(current_user) + @approved = approved + end + + def handle_response_type(req, res) + handle_approval(@approved, req, res) + end + + def handle_approval(approved, req, res) + if approved + approved!(req, res) + else + req.access_denied! + end + end + + # TODO: Add support for request object and auth code + def approved!(req, res) + auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: @o_auth_application, user: @user) + auth.scopes << @scopes + handle_approved_response_type(auth, req, res) + res.approve! + end + + def handle_approved_response_type(auth, req, res) + response_types = Array(req.response_type) + handle_approved_access_token(auth, res, response_types) + handle_approved_id_token(auth, req, res, response_types) + end + + def handle_approved_access_token(auth, res, response_types) + return unless response_types.include?(:token) + res.access_token = auth.create_access_token + end + + def handle_approved_id_token(auth, req, res, response_types) + return unless response_types.include?(:id_token) + id_token = auth.create_id_token(req.nonce) + access_token_value = res.respond_to?(:access_token) ? res.access_token : nil + res.id_token = id_token.to_jwt(code: nil, access_token: access_token_value) + end + end + end + end +end diff --git a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb new file mode 100644 index 000000000..69667cd0e --- /dev/null +++ b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb @@ -0,0 +1,13 @@ +module Api + module OpenidConnect + module AuthorizationPoint + class EndpointStartPoint < Endpoint + def handle_response_type(req, _res) + @response_type = req.response_type + end + + # TODO: buildRequestObject(req) + end + end + end +end diff --git a/lib/api/openid_connect/id_token_config.rb b/lib/api/openid_connect/id_token_config.rb new file mode 100644 index 000000000..d046d4b7a --- /dev/null +++ b/lib/api/openid_connect/id_token_config.rb @@ -0,0 +1,13 @@ +module Api + module OpenidConnect + class IdTokenConfig + @@key = OpenSSL::PKey::RSA.new(2048) + def self.public_key + @@key.public_key + end + def self.private_key + @@key + end + end + end +end diff --git a/lib/api/openid_connect/protected_resource_endpoint.rb b/lib/api/openid_connect/protected_resource_endpoint.rb new file mode 100644 index 000000000..ca9f82ffe --- /dev/null +++ b/lib/api/openid_connect/protected_resource_endpoint.rb @@ -0,0 +1,15 @@ +module Api + module OpenidConnect + module ProtectedResourceEndpoint + attr_reader :current_token + + def require_access_token(*required_scopes) + @current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN] + raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user") unless + @current_token && @current_token.authorization + raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope) unless + @current_token.authorization.try(:accessible?, required_scopes) + end + end + end +end diff --git a/lib/api/openid_connect/token_endpoint.rb b/lib/api/openid_connect/token_endpoint.rb new file mode 100644 index 000000000..eb8937436 --- /dev/null +++ b/lib/api/openid_connect/token_endpoint.rb @@ -0,0 +1,73 @@ +module Api + module OpenidConnect + class TokenEndpoint + attr_accessor :app + delegate :call, to: :app + + def initialize + @app = Rack::OAuth2::Server::Token.new do |req, res| + o_auth_app = retrieve_client(req) + if app_valid?(o_auth_app, req) + handle_flows(o_auth_app, req, res) + else + req.invalid_client! + end + end + end + + def handle_flows(o_auth_app, req, res) + case req.grant_type + when :password + handle_password_flow(o_auth_app, req, res) + when :refresh_token + handle_refresh_flow(req, res) + else + req.unsupported_grant_type! + end + end + + def handle_password_flow(o_auth_app, req, res) + user = User.find_for_database_authentication(username: req.username) + if user + if user.valid_password?(req.password) + auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: o_auth_app, user: user) + build_auth_and_access_token(auth, req, res) + else + req.invalid_grant! + end + else + req.invalid_grant! # TODO: Change to user login: Perhaps redirect_to login_path? + end + end + + def build_auth_and_access_token(auth, req, res) + scope_list = req.scope.map { |scope_name| + OpenidConnect::Scope.find_by(name: scope_name).tap do |scope| + req.invalid_scope! "Unknown scope: #{scope}" unless scope + end + } # TODO: Check client scope permissions + auth.scopes << scope_list + res.access_token = auth.create_access_token + end + + def handle_refresh_flow(req, res) + # Handle as if scope request was omitted even if provided. + # See https://tools.ietf.org/html/rfc6749#section-6 for handling + auth = Api::OpenidConnect::Authorization.find_by_refresh_token req.client_id, req.refresh_token + if auth + res.access_token = auth.create_access_token + else + req.invalid_grant! + end + end + + def retrieve_client(req) + Api::OpenidConnect::OAuthApplication.find_by client_id: req.client_id + end + + def app_valid?(o_auth_app, req) + o_auth_app.client_secret == req.client_secret + end + end + end +end diff --git a/lib/openid_connect/authorization_point/endpoint.rb b/lib/openid_connect/authorization_point/endpoint.rb deleted file mode 100644 index a65974273..000000000 --- a/lib/openid_connect/authorization_point/endpoint.rb +++ /dev/null @@ -1,58 +0,0 @@ -module OpenidConnect - module AuthorizationPoint - class Endpoint - attr_accessor :app, :user, :o_auth_application, :redirect_uri, :response_type, - :scopes, :_request_, :request_uri, :request_object, :nonce - delegate :call, to: :app - - def initialize(current_user) - @user = current_user - @app = Rack::OAuth2::Server::Authorize.new do |req, res| - build_attributes(req, res) - if OAuthApplication.available_response_types.include? Array(req.response_type).map(&:to_s).join(" ") - handle_response_type(req, res) - else - req.unsupported_response_type! - end - end - end - - def build_attributes(req, res) - build_client(req) - build_redirect_uri(req, res) - verify_nonce(req, res) - build_scopes(req) - end - - def handle_response_type(_req, _res) - # Implemented by subclass - end - - private - - def build_client(req) - @o_auth_application = OAuthApplication.find_by_client_id(req.client_id) || req.bad_request! - end - - def build_redirect_uri(req, res) - res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@o_auth_application.redirect_uris) - end - - def verify_nonce(req, res) - if res.protocol_params_location == :fragment && req.nonce.blank? - req.invalid_request! "nonce required" - end - end - - def build_scopes(req) - @scopes = req.scope.map {|scope_name| - OpenidConnect::Scope.where(name: scope_name).first.tap do |scope| - req.invalid_scope! "Unknown scope: #{scope}" unless scope - end - } - end - - # TODO: buildResponseType(req) - end - end -end diff --git a/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb deleted file mode 100644 index 7a6b83183..000000000 --- a/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ /dev/null @@ -1,38 +0,0 @@ -module OpenidConnect - module AuthorizationPoint - class EndpointConfirmationPoint < Endpoint - def initialize(current_user, approved=false) - super(current_user) - @approved = approved - end - - def handle_response_type(req, res) - handle_approval(@approved, req, res) - end - - def handle_approval(approved, req, res) - if approved - approved!(req, res) - else - req.access_denied! - end - end - - # TODO: Add support for request object and auth code - def approved!(req, res) - auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: @o_auth_application, user: @user) - auth.scopes << @scopes - response_types = Array(req.response_type) - if response_types.include?(:token) - res.access_token = auth.create_access_token - end - if response_types.include?(:id_token) - id_token = auth.create_id_token(req.nonce) - access_token_value = res.respond_to?(:access_token) ? res.access_token : nil - res.id_token = id_token.to_jwt(code: nil, access_token: access_token_value) - end - res.approve! - end - end - end -end diff --git a/lib/openid_connect/authorization_point/endpoint_start_point.rb b/lib/openid_connect/authorization_point/endpoint_start_point.rb deleted file mode 100644 index 3af4274e7..000000000 --- a/lib/openid_connect/authorization_point/endpoint_start_point.rb +++ /dev/null @@ -1,11 +0,0 @@ -module OpenidConnect - module AuthorizationPoint - class EndpointStartPoint < Endpoint - def handle_response_type(req, _res) - @response_type = req.response_type - end - - # TODO: buildRequestObject(req) - end - end -end diff --git a/lib/openid_connect/id_token_config.rb b/lib/openid_connect/id_token_config.rb deleted file mode 100644 index cac535c03..000000000 --- a/lib/openid_connect/id_token_config.rb +++ /dev/null @@ -1,11 +0,0 @@ -module OpenidConnect - class IdTokenConfig - @@key = OpenSSL::PKey::RSA.new(2048) - def self.public_key - @@key.public_key - end - def self.private_key - @@key - end - end -end diff --git a/lib/openid_connect/protected_resource_endpoint.rb b/lib/openid_connect/protected_resource_endpoint.rb deleted file mode 100644 index abc606981..000000000 --- a/lib/openid_connect/protected_resource_endpoint.rb +++ /dev/null @@ -1,15 +0,0 @@ -module OpenidConnect - module ProtectedResourceEndpoint - attr_reader :current_token - - def require_access_token(*required_scopes) - @current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN] - unless @current_token && @current_token.authorization - raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user") - end - unless @current_token.authorization.try(:accessible?, required_scopes) - raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope) - end - end - end -end diff --git a/lib/openid_connect/token_endpoint.rb b/lib/openid_connect/token_endpoint.rb deleted file mode 100644 index 53347eba2..000000000 --- a/lib/openid_connect/token_endpoint.rb +++ /dev/null @@ -1,67 +0,0 @@ -module OpenidConnect - class TokenEndpoint - attr_accessor :app - delegate :call, to: :app - - def initialize - @app = Rack::OAuth2::Server::Token.new do |req, res| - o_auth_app = retrieve_client(req) - if app_valid?(o_auth_app, req) - handle_flows(o_auth_app, req, res) - else - req.invalid_client! - end - end - end - - def handle_flows(o_auth_app, req, res) - case req.grant_type - when :password - handle_password_flow(o_auth_app, req, res) - when :refresh_token - handle_refresh_flow(req, res) - else - req.unsupported_grant_type! - end - end - - def handle_password_flow(o_auth_app, req, res) - user = User.find_for_database_authentication(username: req.username) - if user - if user.valid_password?(req.password) - scope_list = req.scope.map { |scope_name| - OpenidConnect::Scope.find_by(name: scope_name).tap do |scope| - req.invalid_scope! "Unknown scope: #{scope}" unless scope - end - } # TODO: Check client scope permissions - auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: o_auth_app, user: user) - auth.scopes << scope_list - res.access_token = auth.create_access_token - else - req.invalid_grant! - end - else - req.invalid_grant! # TODO: Change to user login: Perhaps redirect_to login_path? - end - end - - def handle_refresh_flow(req, res) - # Handle as if scope request was omitted even if provided. - # See https://tools.ietf.org/html/rfc6749#section-6 for handling - auth = OpenidConnect::Authorization.find_by_refresh_token req.client_id, req.refresh_token - if auth - res.access_token = auth.create_access_token - else - req.invalid_grant! - end - end - - def retrieve_client(req) - OpenidConnect::OAuthApplication.find_by client_id: req.client_id - end - - def app_valid?(o_auth_app, req) - o_auth_app.client_secret == req.client_secret - end - end -end diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb similarity index 91% rename from spec/controllers/openid_connect/authorizations_controller_spec.rb rename to spec/controllers/api/openid_connect/authorizations_controller_spec.rb index e19b80e21..9719f9015 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -1,12 +1,12 @@ require "spec_helper" -describe OpenidConnect::AuthorizationsController, type: :controller do +describe Api::OpenidConnect::AuthorizationsController, type: :controller do let!(:client) do - OpenidConnect::OAuthApplication.create!( + Api::OpenidConnect::OAuthApplication.create!( client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) end let!(:client_with_multiple_redirects) do - OpenidConnect::OAuthApplication.create!( + Api::OpenidConnect::OAuthApplication.create!( client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/", "http://localhost/"]) end @@ -15,7 +15,7 @@ describe OpenidConnect::AuthorizationsController, type: :controller do before do sign_in :user, alice allow(@controller).to receive(:current_user).and_return(alice) - OpenidConnect::Scope.create!(name: "openid") + Api::OpenidConnect::Scope.create!(name: "openid") end describe "#new" do @@ -94,7 +94,7 @@ describe OpenidConnect::AuthorizationsController, type: :controller do end end context "when already authorized" do - let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice) } + let!(:auth) { Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice) } context "when valid parameters are passed" do before do @@ -106,9 +106,9 @@ describe OpenidConnect::AuthorizationsController, type: :controller do expect(response.location).to have_content("id_token=") encoded_id_token = response.location[/(?<=id_token=)[^&]+/] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig.public_key expect(decoded_token.nonce).to eq("4130930983") - expect(decoded_token.exp).to be > Time.now.utc.to_i + expect(decoded_token.exp).to be > Time.zone.now.utc.to_i end it "should return the passed in state" do @@ -133,15 +133,15 @@ describe OpenidConnect::AuthorizationsController, type: :controller do it "should return the id token in a fragment" do encoded_id_token = response.location[/(?<=id_token=)[^&]+/] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig.public_key expect(decoded_token.nonce).to eq("4180930983") - expect(decoded_token.exp).to be > Time.now.utc.to_i + expect(decoded_token.exp).to be > Time.zone.now.utc.to_i end it "should return a valid access token in a fragment" do encoded_id_token = response.location[/(?<=id_token=)[^&]+/] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig.public_key access_token = response.location[/(?<=access_token=)[^&]+/] access_token_check_num = UrlSafeBase64.encode64(OpenSSL::Digest::SHA256.digest(access_token)[0, 128 / 8]) expect(decoded_token.at_hash).to eq(access_token_check_num) @@ -164,9 +164,9 @@ describe OpenidConnect::AuthorizationsController, type: :controller do expect(response.location).to have_content("id_token=") encoded_id_token = response.location[/(?<=id_token=)[^&]+/] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig.public_key expect(decoded_token.nonce).to eq("4180930983") - expect(decoded_token.exp).to be > Time.now.utc.to_i + expect(decoded_token.exp).to be > Time.zone.now.utc.to_i end it "should return the passed in state" do diff --git a/spec/controllers/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb similarity index 96% rename from spec/controllers/openid_connect/clients_controller_spec.rb rename to spec/controllers/api/openid_connect/clients_controller_spec.rb index 2bd3cbca4..5326fa32d 100644 --- a/spec/controllers/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe OpenidConnect::ClientsController, type: :controller do +describe Api::OpenidConnect::ClientsController, type: :controller do describe "#create" do context "when valid parameters are passed" do it "should return a client id" do diff --git a/spec/controllers/openid_connect/discovery_controller_spec.rb b/spec/controllers/api/openid_connect/discovery_controller_spec.rb similarity index 87% rename from spec/controllers/openid_connect/discovery_controller_spec.rb rename to spec/controllers/api/openid_connect/discovery_controller_spec.rb index 6a30d9c32..f71126462 100644 --- a/spec/controllers/openid_connect/discovery_controller_spec.rb +++ b/spec/controllers/api/openid_connect/discovery_controller_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe OpenidConnect::DiscoveryController, type: :controller do +describe Api::OpenidConnect::DiscoveryController, type: :controller do describe "#webfinger" do before do get :webfinger, resource: "http://test.host/bob" @@ -8,7 +8,7 @@ describe OpenidConnect::DiscoveryController, type: :controller do it "should return a url to the openid-configuration" do json_body = JSON.parse(response.body) - expect(json_body["links"].first["href"]).to eq("http://test.host/openid_connect") + expect(json_body["links"].first["href"]).to eq("http://test.host/api/openid_connect") end it "should return the resource in the subject" do diff --git a/spec/controllers/openid_connect/id_tokens_controller_spec.rb b/spec/controllers/api/openid_connect/id_tokens_controller_spec.rb similarity index 70% rename from spec/controllers/openid_connect/id_tokens_controller_spec.rb rename to spec/controllers/api/openid_connect/id_tokens_controller_spec.rb index 1e9e3548b..06930c45b 100644 --- a/spec/controllers/openid_connect/id_tokens_controller_spec.rb +++ b/spec/controllers/api/openid_connect/id_tokens_controller_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe OpenidConnect::IdTokensController, type: :controller do +describe Api::OpenidConnect::IdTokensController, type: :controller do describe "#jwks" do before do get :jwks @@ -13,7 +13,7 @@ describe OpenidConnect::IdTokensController, type: :controller do JSON::JWK.decode jwk end public_key = public_keys.first - expect(OpenidConnect::IdTokenConfig.private_key.public_key.to_s).to eq(public_key.to_s) + expect(Api::OpenidConnect::IdTokenConfig.private_key.public_key.to_s).to eq(public_key.to_s) end end end diff --git a/spec/integration/api/users_controller_spec.rb b/spec/integration/api/users_controller_spec.rb new file mode 100644 index 000000000..53f9b4607 --- /dev/null +++ b/spec/integration/api/users_controller_spec.rb @@ -0,0 +1,27 @@ +require "spec_helper" + +describe Api::V0::UsersController do + # TODO: Replace with factory + let!(:client) do + Api::OpenidConnect::OAuthApplication.create!( + client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) + end + let(:auth_with_read) do + auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) + auth.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "read")] + auth + end + let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } + + describe "#show" do + before do + get api_v0_user_path, access_token: access_token_with_read + end + + it "shows the info" do + json_body = JSON.parse(response.body) + expect(json_body["username"]).to eq(alice.username) + expect(json_body["email"]).to eq(alice.email) + end + end +end diff --git a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb similarity index 86% rename from spec/lib/openid_connect/protected_resource_endpoint_spec.rb rename to spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb index cd344c3a9..f3933a5ee 100644 --- a/spec/lib/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb @@ -1,14 +1,14 @@ require "spec_helper" -describe OpenidConnect::ProtectedResourceEndpoint, type: :request do +describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do # TODO: Replace with factory let!(:client) do - OpenidConnect::OAuthApplication.create!( + Api::OpenidConnect::OAuthApplication.create!( client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) end let(:auth_with_read) do - auth = OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) - auth.scopes << [OpenidConnect::Scope.find_or_create_by(name: "read")] + auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) + auth.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "read")] auth end let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } @@ -27,12 +27,14 @@ describe OpenidConnect::ProtectedResourceEndpoint, type: :request do end context "when no access token is provided" do - it "should respond with a 401 Unauthorized response" do + before do get api_v0_user_path + end + + it "should respond with a 401 Unauthorized response" do expect(response.status).to be(401) end it "should have an auth-scheme value of Bearer" do - get api_v0_user_path expect(response.headers["WWW-Authenticate"]).to include("Bearer") end end diff --git a/spec/lib/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb similarity index 71% rename from spec/lib/openid_connect/token_endpoint_spec.rb rename to spec/lib/api/openid_connect/token_endpoint_spec.rb index 7af40fd21..21eda5e87 100644 --- a/spec/lib/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -1,20 +1,20 @@ require "spec_helper" -describe OpenidConnect::TokenEndpoint, type: :request do +describe Api::OpenidConnect::TokenEndpoint, type: :request do let!(:client) do - OpenidConnect::OAuthApplication.create!( + Api::OpenidConnect::OAuthApplication.create!( redirect_uris: ["http://localhost"], client_name: "diaspora client") end - let(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) } + let(:auth) { Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) } before do - OpenidConnect::Scope.find_or_create_by(name: "read") + Api::OpenidConnect::Scope.find_or_create_by(name: "read") end describe "the password grant type" do context "when the username field is missing" do it "should return an invalid request error" do - post openid_connect_access_tokens_path, grant_type: "password", password: "bluepin7", + post api_openid_connect_access_tokens_path, grant_type: "password", password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "'username' required" end @@ -22,7 +22,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the password field is missing" do it "should return an invalid request error" do - post openid_connect_access_tokens_path, grant_type: "password", username: "bob", + post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "'password' required" end @@ -30,7 +30,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the username does not match an existing user" do it "should return an invalid request error" do - post openid_connect_access_tokens_path, grant_type: "password", username: "randomnoexist", + post api_openid_connect_access_tokens_path, grant_type: "password", username: "randomnoexist", password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "invalid_grant" end @@ -38,7 +38,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the password is invalid" do it "should return an invalid request error" do - post openid_connect_access_tokens_path, grant_type: "password", username: "bob", + post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "wrongpassword", client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "invalid_grant" end @@ -46,7 +46,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the client_secret doesn't match" do it "should return an invalid client error" do - post openid_connect_access_tokens_path, grant_type: "password", username: "bob", + post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "bluepin7", client_id: client.client_id, client_secret: "client.client_secret", scope: "read" expect(response.body).to include "invalid_client" end @@ -54,7 +54,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the request is valid" do it "should return an access token" do - post openid_connect_access_tokens_path, grant_type: "password", username: "bob", + post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read" json = JSON.parse(response.body) expect(json.keys).to include "expires_in" @@ -65,16 +65,18 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when there are duplicate fields" do it "should return an invalid request error" do - post openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "bluepin7", - username: "bob", password: "bluepin6", client_id: client.client_id, client_secret: client.client_secret, scope: "read" + post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "bluepin7", + username: "bob", password: "bluepin6", client_id: client.client_id, client_secret: client.client_secret, + scope: "read" expect(response.body).to include "invalid_grant" end end context "when the client is unregistered" do it "should return an error" do - post openid_connect_access_tokens_path, grant_type: "password", username: "bob", - password: "bluepin7", client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret, scope: "read" + post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", + password: "bluepin7", client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret, + scope: "read" expect(response.body).to include "invalid_client" end end @@ -84,7 +86,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do describe "an unsupported grant type" do it "should return an unsupported grant type error" do - post openid_connect_access_tokens_path, grant_type: "noexistgrant", username: "bob", + post api_openid_connect_access_tokens_path, grant_type: "noexistgrant", username: "bob", password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read" expect(response.body).to include "unsupported_grant_type" end @@ -93,7 +95,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do describe "the refresh token flow" do context "when the refresh token is valid" do it "should return an access token" do - post openid_connect_access_tokens_path, grant_type: "refresh_token", + post api_openid_connect_access_tokens_path, grant_type: "refresh_token", client_id: client.client_id, client_secret: client.client_secret, refresh_token: auth.refresh_token json = JSON.parse(response.body) expect(response.body).to include "expires_in" @@ -104,7 +106,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the refresh token is not valid" do it "should return an invalid grant error" do - post openid_connect_access_tokens_path, grant_type: "refresh_token", + post api_openid_connect_access_tokens_path, grant_type: "refresh_token", client_id: client.client_id, client_secret: client.client_secret, refresh_token: "123456" expect(response.body).to include "invalid_grant" end @@ -112,7 +114,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the client is unregistered" do it "should return an error" do - post openid_connect_access_tokens_path, grant_type: "refresh_token", refresh_token: auth.refresh_token, + post api_openid_connect_access_tokens_path, grant_type: "refresh_token", refresh_token: auth.refresh_token, client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret expect(response.body).to include "invalid_client" end @@ -120,7 +122,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the refresh_token field is missing" do it "should return an invalid request error" do - post openid_connect_access_tokens_path, grant_type: "refresh_token", + post api_openid_connect_access_tokens_path, grant_type: "refresh_token", client_id: client.client_id, client_secret: client.client_secret expect(response.body).to include "'refresh_token' required" end @@ -128,7 +130,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do context "when the client_secret doesn't match" do it "should return an invalid client error" do - post openid_connect_access_tokens_path, grant_type: "refresh_token", refresh_token: auth.refresh_token, + post api_openid_connect_access_tokens_path, grant_type: "refresh_token", refresh_token: auth.refresh_token, client_id: client.client_id, client_secret: "client.client_secret" expect(response.body).to include "invalid_client" end From e5932968fddc4db07ffd7aae92673df0e0e98a76 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 31 Jul 2015 01:35:50 +0900 Subject: [PATCH 031/105] Add support for authorization code flow --- .../api/openid_connect/authorization.rb | 15 +++- .../api/openid_connect/o_auth_application.rb | 2 +- .../20150708153926_create_authorizations.rb | 2 + db/schema.rb | 2 + features/desktop/oidc_auth_code_flow.feature | 27 +++++++ features/step_definitions/auth_code_steps.rb | 32 +++++++++ .../endpoint_confirmation_point.rb | 14 +++- lib/api/openid_connect/token_endpoint.rb | 8 +++ .../authorizations_controller_spec.rb | 40 ++++++++++- .../api/openid_connect/token_endpoint_spec.rb | 70 ++++++++++++++++++- 10 files changed, 202 insertions(+), 10 deletions(-) create mode 100644 features/desktop/oidc_auth_code_flow.feature create mode 100644 features/step_definitions/auth_code_steps.rb diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index 88a511fbe..90a225845 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -15,6 +15,8 @@ module Api before_validation :setup, on: :create + scope :with_redirect_uri, ->(given_uri) { where redirect_uri: given_uri } + def setup self.refresh_token = SecureRandom.hex(32) end @@ -25,12 +27,18 @@ module Api end end + def create_code + self.code = SecureRandom.hex(32) + save + self.code + end + def create_access_token o_auth_access_tokens.create!.bearer_token # TODO: Add support for request object end - def create_id_token(nonce) + def create_id_token(nonce=nil) id_tokens.create!(nonce: nonce) end @@ -44,6 +52,11 @@ module Api o_auth_applications: {client_id: client_id}, refresh_token: refresh_token) end + def self.use_code(code) + auth = find_by(code: code) + auth.code = nil if auth # Remove auth code if found so it can't be reused + auth + end # TODO: Consider splitting into subclasses by flow type end end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 2a97665a2..3cc2dadbc 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -30,7 +30,7 @@ module Api class << self def available_response_types - ["id_token", "id_token token"] + ["id_token", "id_token token", "code"] end def register!(registrar) diff --git a/db/migrate/20150708153926_create_authorizations.rb b/db/migrate/20150708153926_create_authorizations.rb index 24acdc081..c99fa0e85 100644 --- a/db/migrate/20150708153926_create_authorizations.rb +++ b/db/migrate/20150708153926_create_authorizations.rb @@ -4,6 +4,8 @@ class CreateAuthorizations < ActiveRecord::Migration t.belongs_to :user, index: true t.belongs_to :o_auth_application, index: true t.string :refresh_token + t.string :code + t.string :redirect_uri t.timestamps null: false end diff --git a/db/schema.rb b/db/schema.rb index dfaf0ed3b..21bb99e43 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -67,6 +67,8 @@ ActiveRecord::Schema.define(version: 20150724152052) do t.integer "user_id", limit: 4 t.integer "o_auth_application_id", limit: 4 t.string "refresh_token", limit: 255 + t.string "code", limit: 255 + t.string "redirect_uri", limit: 255 t.datetime "created_at", null: false t.datetime "updated_at", null: false end diff --git a/features/desktop/oidc_auth_code_flow.feature b/features/desktop/oidc_auth_code_flow.feature new file mode 100644 index 000000000..49dd10fe6 --- /dev/null +++ b/features/desktop/oidc_auth_code_flow.feature @@ -0,0 +1,27 @@ +@javascript +Feature: Access protected resources using auth code flow + Background: + Given a user with username "kent" + And all scopes exist + + Scenario: Invalid client id to auth endpoint + When I register a new client + And I send a post request from that client to the code flow authorization endpoint using a invalid client id + And I sign in as "kent@kent.kent" + Then I should see an "bad_request" error + + Scenario: Application is denied authorization + When I register a new client + And I send a post request from that client to the code flow authorization endpoint + And I sign in as "kent@kent.kent" + And I deny authorization to the client + Then I should not see any tokens in the redirect url + + Scenario: Application is authorized + When I register a new client + And I send a post request from that client to the code flow authorization endpoint + And I sign in as "kent@kent.kent" + And I give my consent and authorize the client + And I parse the auth code and create a request to the token endpoint + And I parse the tokens and use it obtain user info + Then I should receive "kent"'s id, username, and email diff --git a/features/step_definitions/auth_code_steps.rb b/features/step_definitions/auth_code_steps.rb new file mode 100644 index 000000000..8f690a23d --- /dev/null +++ b/features/step_definitions/auth_code_steps.rb @@ -0,0 +1,32 @@ +o_auth_query_params = %i( + redirect_uri=http://localhost:3000 + response_type=code + scope=openid%20read + nonce=hello + state=hi +).join("&") + +Given /^I send a post request from that client to the code flow authorization endpoint$/ do + client_json = JSON.parse(last_response.body) + @client_id = client_json['o_auth_application']['client_id'] + @client_secret = client_json['o_auth_application']['client_secret'] + visit new_api_openid_connect_authorization_path + + "?client_id=#{@client_id}&#{o_auth_query_params}" +end + +Given /^I send a post request from that client to the code flow authorization endpoint using a invalid client id/ do + visit new_api_openid_connect_authorization_path + "?client_id=randomid&#{o_auth_query_params}" +end + +When /^I parse the auth code and create a request to the token endpoint$/ do + code = current_url[/(?<=code=)[^&]+/] + post api_openid_connect_access_tokens_path, code: code, + redirect_uri: "http://localhost:3000", grant_type: "authorization_code", + client_id: @client_id, client_secret: @client_secret +end + +When /^I parse the tokens and use it obtain user info$/ do + client_json = JSON.parse(last_response.body) + access_token = client_json["access_token"] + get api_v0_user_path, access_token: access_token +end diff --git a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb index a500b9dcc..e15053e0e 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -19,9 +19,10 @@ module Api end end - # TODO: Add support for request object and auth code + # TODO: Add support for request object def approved!(req, res) - auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: @o_auth_application, user: @user) + auth = OpenidConnect::Authorization.find_or_create_by( + o_auth_application: @o_auth_application, user: @user, redirect_uri: @redirect_uri) auth.scopes << @scopes handle_approved_response_type(auth, req, res) res.approve! @@ -29,10 +30,16 @@ module Api def handle_approved_response_type(auth, req, res) response_types = Array(req.response_type) + handle_approved_auth_code(auth, res, response_types) handle_approved_access_token(auth, res, response_types) handle_approved_id_token(auth, req, res, response_types) end + def handle_approved_auth_code(auth, res, response_types) + return unless response_types.include?(:code) + res.code = auth.create_code + end + def handle_approved_access_token(auth, res, response_types) return unless response_types.include?(:token) res.access_token = auth.create_access_token @@ -41,8 +48,9 @@ module Api def handle_approved_id_token(auth, req, res, response_types) return unless response_types.include?(:id_token) id_token = auth.create_id_token(req.nonce) + auth_code_value = res.respond_to?(:code) ? res.code : nil access_token_value = res.respond_to?(:access_token) ? res.access_token : nil - res.id_token = id_token.to_jwt(code: nil, access_token: access_token_value) + res.id_token = id_token.to_jwt(code: auth_code_value, access_token: access_token_value) end end end diff --git a/lib/api/openid_connect/token_endpoint.rb b/lib/api/openid_connect/token_endpoint.rb index eb8937436..7ae0e7dbd 100644 --- a/lib/api/openid_connect/token_endpoint.rb +++ b/lib/api/openid_connect/token_endpoint.rb @@ -21,6 +21,14 @@ module Api handle_password_flow(o_auth_app, req, res) when :refresh_token handle_refresh_flow(req, res) + when :authorization_code + auth = Api::OpenidConnect::Authorization.with_redirect_uri(req.redirect_uri).use_code(req.code) + req.invalid_grant! if auth.blank? + res.access_token = auth.create_access_token + if auth.accessible?(Api::OpenidConnect::Scope.find_by(name: "openid")) + id_token = auth.create_id_token + res.id_token = id_token.to_jwt(access_token: res.access_token) + end else req.unsupported_grant_type! end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 9719f9015..a8d5e3405 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -49,7 +49,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do context "when redirect uri is missing" do context "when only one redirect URL is pre-registered" do - it "should return a form pager" do + it "should return a form page" do # Note this intentionally behavior diverts from OIDC spec http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest # When client has only one redirect uri registered, only that redirect uri can be used. Hence, # we should implicitly assume the client wants to use that registered URI. @@ -94,7 +94,8 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do end end context "when already authorized" do - let!(:auth) { Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice) } + let!(:auth) { Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice, + redirect_uri: "http://localhost:3000/") } context "when valid parameters are passed" do before do @@ -188,5 +189,40 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do end end end + + context "when code" do + before do + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "code", + scope: "openid", nonce: 418_093_098_3, state: 418_093_098_3 + end + + context "when authorization is approved" do + before do + post :create, approve: "true" + end + + it "should return the code" do + expect(response.location).to have_content("code") + end + + it "should return the passed in state" do + expect(response.location).to have_content("state=4180930983") + end + end + + context "when authorization is denied" do + before do + post :create, approve: "false" + end + + it "should return an error" do + expect(response.location).to have_content("error") + end + + it "should NOT contain code" do + expect(response.location).to_not have_content("code") + end + end + end end end diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index 21eda5e87..85219cb00 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -3,14 +3,78 @@ require "spec_helper" describe Api::OpenidConnect::TokenEndpoint, type: :request do let!(:client) do Api::OpenidConnect::OAuthApplication.create!( - redirect_uris: ["http://localhost"], client_name: "diaspora client") + redirect_uris: ["http://localhost:3000/"], client_name: "diaspora client") end - let(:auth) { Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) } + let!(:auth) { + Api::OpenidConnect::Authorization.find_or_create_by( + o_auth_application: client, user: bob, redirect_uri: "http://localhost:3000/") + } + let!(:code) { auth.create_code } before do Api::OpenidConnect::Scope.find_or_create_by(name: "read") end + describe "the authorization code grant type" do + context "when the authorization code is valid" do + before do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + client_id: client.client_id, client_secret: client.client_secret, + redirect_uri: "http://localhost:3000/", code: code + end + + it "should return a valid id token" do + json = JSON.parse(response.body) + encoded_id_token = json["id_token"] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, + Api::OpenidConnect::IdTokenConfig.public_key + expect(decoded_token.exp).to be > Time.zone.now.utc.to_i + end + + it "should return a valid access token" do + json = JSON.parse(response.body) + encoded_id_token = json["id_token"] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, + Api::OpenidConnect::IdTokenConfig.public_key + access_token = json["access_token"] + access_token_check_num = UrlSafeBase64.encode64(OpenSSL::Digest::SHA256.digest(access_token)[0, 128 / 8]) + expect(decoded_token.at_hash).to eq(access_token_check_num) + end + end + + context "when the authorization code is not valid" do + it "should return an invalid grant error" do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + client_id: client.client_id, client_secret: client.client_secret, code: "123456" + expect(response.body).to include "invalid_grant" + end + end + + context "when the client is unregistered" do + it "should return an error" do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", code: auth.refresh_token, + client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret + expect(response.body).to include "invalid_client" + end + end + + context "when the code field is missing" do + it "should return an invalid request error" do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + client_id: client.client_id, client_secret: client.client_secret + expect(response.body).to include "invalid_request" + end + end + + context "when the client_secret doesn't match" do + it "should return an invalid client error" do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", code: auth.refresh_token, + client_id: client.client_id, client_secret: "client.client_secret" + expect(response.body).to include "invalid_client" + end + end + end + describe "the password grant type" do context "when the username field is missing" do it "should return an invalid request error" do @@ -92,7 +156,7 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do end end - describe "the refresh token flow" do + describe "the refresh token grant type" do context "when the refresh token is valid" do it "should return an access token" do post api_openid_connect_access_tokens_path, grant_type: "refresh_token", From 2be932ceffaa4bac23668630706631f16858b01f Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 31 Jul 2015 01:46:14 +0900 Subject: [PATCH 032/105] Delete password flow --- features/desktop/oauth_password_flow.feature | 21 ------ .../step_definitions/oidc_common_steps.rb | 30 ++++++++ .../step_definitions/password_flow_steps.rb | 48 ------------ lib/api/openid_connect/token_endpoint.rb | 16 ---- .../api/openid_connect/token_endpoint_spec.rb | 73 ------------------- 5 files changed, 30 insertions(+), 158 deletions(-) delete mode 100644 features/desktop/oauth_password_flow.feature create mode 100644 features/step_definitions/oidc_common_steps.rb diff --git a/features/desktop/oauth_password_flow.feature b/features/desktop/oauth_password_flow.feature deleted file mode 100644 index 4bde1b08d..000000000 --- a/features/desktop/oauth_password_flow.feature +++ /dev/null @@ -1,21 +0,0 @@ -Feature: Access protected resources using password flow - Background: - Given a user with username "kent" - And all scopes exist - - Scenario: Invalid credentials to token endpoint - When I register a new client - And I send a post request from that client to the password flow token endpoint using invalid credentials - Then I should receive an "invalid_grant" error - - Scenario: Invalid bearer tokens sent - When I register a new client - And I send a post request from that client to the password flow token endpoint using "kent"'s credentials - And I use invalid bearer tokens to access user info - Then I should receive an "invalid_token" error - - Scenario: Valid password flow - When I register a new client - And I send a post request from that client to the password flow token endpoint using "kent"'s credentials - And I use received valid bearer tokens to access user info - Then I should receive "kent"'s id, username, and email diff --git a/features/step_definitions/oidc_common_steps.rb b/features/step_definitions/oidc_common_steps.rb new file mode 100644 index 000000000..1519e26fc --- /dev/null +++ b/features/step_definitions/oidc_common_steps.rb @@ -0,0 +1,30 @@ +Given(/^all scopes exist$/) do + Api::OpenidConnect::Scope.find_or_create_by(name: "openid") + Api::OpenidConnect::Scope.find_or_create_by(name: "read") +end + +When /^I register a new client$/ do + post api_openid_connect_clients_path, redirect_uris: ["http://localhost:3000"], client_name: "diaspora client" +end + +When /^I use received valid bearer tokens to access user info$/ do + access_token_json = JSON.parse(last_response.body) + get api_v0_user_path, access_token: access_token_json["access_token"] +end + +When /^I use invalid bearer tokens to access user info$/ do + get api_v0_user_path, access_token: SecureRandom.hex(32) +end + +Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username| + user_info_json = JSON.parse(last_response.body) + user = User.find_by_username(username) + expect(user_info_json["username"]).to have_content(user.username) + expect(user_info_json["language"]).to have_content(user.language) + expect(user_info_json["email"]).to have_content(user.email) +end + +Then /^I should receive an "([^\"]*)" error$/ do |error_message| + user_info_json = JSON.parse(last_response.body) + expect(user_info_json["error"]).to have_content(error_message) +end diff --git a/features/step_definitions/password_flow_steps.rb b/features/step_definitions/password_flow_steps.rb index 2a68d1082..e69de29bb 100644 --- a/features/step_definitions/password_flow_steps.rb +++ b/features/step_definitions/password_flow_steps.rb @@ -1,48 +0,0 @@ -Given(/^all scopes exist$/) do - Api::OpenidConnect::Scope.find_or_create_by(name: "openid") - Api::OpenidConnect::Scope.find_or_create_by(name: "read") -end - -When /^I register a new client$/ do - post api_openid_connect_clients_path, redirect_uris: ["http://localhost:3000"], client_name: "diaspora client" -end - -Given /^I send a post request from that client to the password flow token endpoint using "([^\"]*)"'s credentials$/ do |username| - client_json = JSON.parse(last_response.body) - user = User.find_by(username: username) - post api_openid_connect_access_tokens_path, grant_type: "password", username: user.username, - password: "password", # Password has been hard coded as all test accounts seem to have a password of "password" - client_id: client_json["o_auth_application"]["client_id"], - client_secret: client_json["o_auth_application"]["client_secret"], - scope: "read" -end - -Given /^I send a post request from that client to the password flow token endpoint using invalid credentials$/ do - client_json = JSON.parse(last_response.body) - post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "wrongpassword", - client_id: client_json["o_auth_application"]["client_id"], - client_secret: client_json["o_auth_application"]["client_secret"], - scope: "read" -end - -When /^I use received valid bearer tokens to access user info$/ do - access_token_json = JSON.parse(last_response.body) - get api_v0_user_path, access_token: access_token_json["access_token"] -end - -When /^I use invalid bearer tokens to access user info$/ do - get api_v0_user_path, access_token: SecureRandom.hex(32) -end - -Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username| - user_info_json = JSON.parse(last_response.body) - user = User.find_by_username(username) - expect(user_info_json["username"]).to have_content(user.username) - expect(user_info_json["language"]).to have_content(user.language) - expect(user_info_json["email"]).to have_content(user.email) -end - -Then /^I should receive an "([^\"]*)" error$/ do |error_message| - user_info_json = JSON.parse(last_response.body) - expect(user_info_json["error"]).to have_content(error_message) -end diff --git a/lib/api/openid_connect/token_endpoint.rb b/lib/api/openid_connect/token_endpoint.rb index 7ae0e7dbd..1f592c03c 100644 --- a/lib/api/openid_connect/token_endpoint.rb +++ b/lib/api/openid_connect/token_endpoint.rb @@ -17,8 +17,6 @@ module Api def handle_flows(o_auth_app, req, res) case req.grant_type - when :password - handle_password_flow(o_auth_app, req, res) when :refresh_token handle_refresh_flow(req, res) when :authorization_code @@ -34,20 +32,6 @@ module Api end end - def handle_password_flow(o_auth_app, req, res) - user = User.find_for_database_authentication(username: req.username) - if user - if user.valid_password?(req.password) - auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: o_auth_app, user: user) - build_auth_and_access_token(auth, req, res) - else - req.invalid_grant! - end - else - req.invalid_grant! # TODO: Change to user login: Perhaps redirect_to login_path? - end - end - def build_auth_and_access_token(auth, req, res) scope_list = req.scope.map { |scope_name| OpenidConnect::Scope.find_by(name: scope_name).tap do |scope| diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index 85219cb00..69228bcaf 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -75,79 +75,6 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do end end - describe "the password grant type" do - context "when the username field is missing" do - it "should return an invalid request error" do - post api_openid_connect_access_tokens_path, grant_type: "password", password: "bluepin7", - client_id: client.client_id, client_secret: client.client_secret, scope: "read" - expect(response.body).to include "'username' required" - end - end - - context "when the password field is missing" do - it "should return an invalid request error" do - post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", - client_id: client.client_id, client_secret: client.client_secret, scope: "read" - expect(response.body).to include "'password' required" - end - end - - context "when the username does not match an existing user" do - it "should return an invalid request error" do - post api_openid_connect_access_tokens_path, grant_type: "password", username: "randomnoexist", - password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read" - expect(response.body).to include "invalid_grant" - end - end - - context "when the password is invalid" do - it "should return an invalid request error" do - post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", - password: "wrongpassword", client_id: client.client_id, client_secret: client.client_secret, scope: "read" - expect(response.body).to include "invalid_grant" - end - end - - context "when the client_secret doesn't match" do - it "should return an invalid client error" do - post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", - password: "bluepin7", client_id: client.client_id, client_secret: "client.client_secret", scope: "read" - expect(response.body).to include "invalid_client" - end - end - - context "when the request is valid" do - it "should return an access token" do - post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", - password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read" - json = JSON.parse(response.body) - expect(json.keys).to include "expires_in" - expect(json["access_token"].length).to eq(64) - expect(json["token_type"]).to eq("bearer") - end - end - - context "when there are duplicate fields" do - it "should return an invalid request error" do - post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "bluepin7", - username: "bob", password: "bluepin6", client_id: client.client_id, client_secret: client.client_secret, - scope: "read" - expect(response.body).to include "invalid_grant" - end - end - - context "when the client is unregistered" do - it "should return an error" do - post api_openid_connect_access_tokens_path, grant_type: "password", username: "bob", - password: "bluepin7", client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret, - scope: "read" - expect(response.body).to include "invalid_client" - end - end - # TODO: Support a way to prevent brute force attacks using rate-limitation - # as specified by RFC 6749 4.3.2 Access Token Request - end - describe "an unsupported grant type" do it "should return an unsupported grant type error" do post api_openid_connect_access_tokens_path, grant_type: "noexistgrant", username: "bob", From d834a1d4d049e5e640479805f5a0a72ccc14b4eb Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 31 Jul 2015 18:02:59 +0900 Subject: [PATCH 033/105] Replace user info endpoint with supported claims The route /api/v0/user/ will now be used as a non-OIDC route. In other words, the /api/v0/user/ will require the "read" scope while /api/openid_connect/user_info/ will require the "openid" scope --- .../openid_connect/discovery_controller.rb | 4 ++-- .../openid_connect/user_info_controller.rb | 19 +++++++++++++++ app/serializers/user_info_serializer.rb | 23 +++++++++++++++++++ app/serializers/user_serializer.rb | 3 --- config/routes.rb | 2 ++ features/step_definitions/auth_code_steps.rb | 2 +- .../step_definitions/implicit_flow_steps.rb | 2 +- .../step_definitions/oidc_common_steps.rb | 10 ++++---- .../authorizations_controller_spec.rb | 6 +++-- spec/integration/api/users_controller_spec.rb | 9 ++++---- .../protected_resource_endpoint_spec.rb | 11 +++++---- 11 files changed, 68 insertions(+), 23 deletions(-) create mode 100644 app/controllers/api/openid_connect/user_info_controller.rb create mode 100644 app/serializers/user_info_serializer.rb delete mode 100644 app/serializers/user_serializer.rb diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index 4d4b56bb5..15ce38738 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -25,8 +25,8 @@ module Api request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), subject_types_supported: %w(public pairwise), id_token_signing_alg_values_supported: %i(RS256), - token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post) - # TODO: claims_supported: ["sub", "iss", "name", "email"] + token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), + claims_supported: %w(sub nickname profile picture zoneinfo) ) end end diff --git a/app/controllers/api/openid_connect/user_info_controller.rb b/app/controllers/api/openid_connect/user_info_controller.rb new file mode 100644 index 000000000..9fb79226d --- /dev/null +++ b/app/controllers/api/openid_connect/user_info_controller.rb @@ -0,0 +1,19 @@ +module Api + module OpenidConnect + class UserInfoController < ApplicationController + include Api::OpenidConnect::ProtectedResourceEndpoint + + before_action do + require_access_token Api::OpenidConnect::Scope.find_by(name: "openid") + end + + def show + render json: current_user, serializer: UserInfoSerializer + end + + def current_user + current_token ? current_token.authorization.user : nil + end + end + end +end diff --git a/app/serializers/user_info_serializer.rb b/app/serializers/user_info_serializer.rb new file mode 100644 index 000000000..3f042380d --- /dev/null +++ b/app/serializers/user_info_serializer.rb @@ -0,0 +1,23 @@ +class UserInfoSerializer < ActiveModel::Serializer + attributes :sub, :nickname, :profile, :picture, :zoneinfo + + def sub + object.diaspora_handle # TODO: Change to proper sub + end + + def nickname + object.name + end + + def profile + File.join(AppConfig.environment.url, "people", object.guid).to_s + end + + def picture + File.join(AppConfig.environment.url, object.image_url).to_s + end + + def zoneinfo + object.language + end +end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb deleted file mode 100644 index b0d134815..000000000 --- a/app/serializers/user_serializer.rb +++ /dev/null @@ -1,3 +0,0 @@ -class UserSerializer < ActiveModel::Serializer - attributes :name, :email, :language, :username -end diff --git a/config/routes.rb b/config/routes.rb index a9739b73a..683e1da17 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -250,6 +250,8 @@ Diaspora::Application.routes.draw do get ".well-known/webfinger", to: "discovery#webfinger" get ".well-known/openid-configuration", to: "discovery#configuration" get "jwks.json", to: "id_tokens#jwks" + + get "user_info", to: "user_info#show" end end end diff --git a/features/step_definitions/auth_code_steps.rb b/features/step_definitions/auth_code_steps.rb index 8f690a23d..a34c284d9 100644 --- a/features/step_definitions/auth_code_steps.rb +++ b/features/step_definitions/auth_code_steps.rb @@ -28,5 +28,5 @@ end When /^I parse the tokens and use it obtain user info$/ do client_json = JSON.parse(last_response.body) access_token = client_json["access_token"] - get api_v0_user_path, access_token: access_token + get api_openid_connect_user_info_path, access_token: access_token end diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb index fad520faa..cb7394da1 100644 --- a/features/step_definitions/implicit_flow_steps.rb +++ b/features/step_definitions/implicit_flow_steps.rb @@ -33,7 +33,7 @@ end When /^I parse the bearer tokens and use it to access user info$/ do access_token = current_url[/(?<=access_token=)[^&]+/] - get api_v0_user_path, access_token: access_token + get api_openid_connect_user_info_path, access_token: access_token end Then /^I should see an "([^\"]*)" error$/ do |error_message| diff --git a/features/step_definitions/oidc_common_steps.rb b/features/step_definitions/oidc_common_steps.rb index 1519e26fc..bcf8be96e 100644 --- a/features/step_definitions/oidc_common_steps.rb +++ b/features/step_definitions/oidc_common_steps.rb @@ -9,19 +9,19 @@ end When /^I use received valid bearer tokens to access user info$/ do access_token_json = JSON.parse(last_response.body) - get api_v0_user_path, access_token: access_token_json["access_token"] + get api_openid_connect_user_info_path, access_token: access_token_json["access_token"] end When /^I use invalid bearer tokens to access user info$/ do - get api_v0_user_path, access_token: SecureRandom.hex(32) + get api_openid_connect_user_info_path, access_token: SecureRandom.hex(32) end Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username| user_info_json = JSON.parse(last_response.body) user = User.find_by_username(username) - expect(user_info_json["username"]).to have_content(user.username) - expect(user_info_json["language"]).to have_content(user.language) - expect(user_info_json["email"]).to have_content(user.email) + user_profile_url = File.join(AppConfig.environment.url, "people", user.guid).to_s + expect(user_info_json["profile"]).to have_content(user_profile_url) + expect(user_info_json["zoneinfo"]).to have_content(user.language) end Then /^I should receive an "([^\"]*)" error$/ do |error_message| diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index a8d5e3405..6663f281e 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -94,8 +94,10 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do end end context "when already authorized" do - let!(:auth) { Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice, - redirect_uri: "http://localhost:3000/") } + let!(:auth) { + Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice, + redirect_uri: "http://localhost:3000/") + } context "when valid parameters are passed" do before do diff --git a/spec/integration/api/users_controller_spec.rb b/spec/integration/api/users_controller_spec.rb index 53f9b4607..7b13f738f 100644 --- a/spec/integration/api/users_controller_spec.rb +++ b/spec/integration/api/users_controller_spec.rb @@ -8,20 +8,21 @@ describe Api::V0::UsersController do end let(:auth_with_read) do auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) - auth.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "read")] + auth.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "openid"), + Api::OpenidConnect::Scope.find_or_create_by(name: "read")] auth end let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } describe "#show" do before do - get api_v0_user_path, access_token: access_token_with_read + get api_openid_connect_user_info_path, access_token: access_token_with_read end it "shows the info" do json_body = JSON.parse(response.body) - expect(json_body["username"]).to eq(alice.username) - expect(json_body["email"]).to eq(alice.email) + expect(json_body["nickname"]).to eq(alice.name) + expect(json_body["profile"]).to eq(File.join(AppConfig.environment.url, "people", alice.guid).to_s) end end end diff --git a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb index f3933a5ee..219cf1c40 100644 --- a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb @@ -8,7 +8,8 @@ describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do end let(:auth_with_read) do auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) - auth.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "read")] + auth.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "openid"), + Api::OpenidConnect::Scope.find_or_create_by(name: "read")] auth end let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } @@ -18,7 +19,7 @@ describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do context "when valid access token is provided" do before do - get api_v0_user_path, access_token: access_token_with_read + get api_openid_connect_user_info_path, access_token: access_token_with_read end it "includes private in the cache-control header" do @@ -28,7 +29,7 @@ describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do context "when no access token is provided" do before do - get api_v0_user_path + get api_openid_connect_user_info_path end it "should respond with a 401 Unauthorized response" do @@ -41,7 +42,7 @@ describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do context "when an invalid access token is provided" do before do - get api_v0_user_path, access_token: invalid_token + get api_openid_connect_user_info_path, access_token: invalid_token end it "should respond with a 401 Unauthorized response" do @@ -60,7 +61,7 @@ describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do context "when authorization has been destroyed" do before do auth_with_read.destroy - get api_v0_user_path, access_token: access_token_with_read + get api_openid_connect_user_info_path, access_token: access_token_with_read end it "should respond with a 401 Unauthorized response" do From 99d6d7b3e72bff956908bcbbe92efc5532a3323f Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sat, 1 Aug 2015 17:09:23 +0900 Subject: [PATCH 034/105] Add pairwise pseudonymous identifier support Squashed commits: [a182de7] Fix pronto/travis errors --- .../openid_connect/discovery_controller.rb | 2 +- .../openid_connect/user_info_controller.rb | 2 +- app/models/api/openid_connect/id_token.rb | 18 ++++++-- .../api/openid_connect/o_auth_application.rb | 26 +++++------ .../pairwise_pseudonymous_identifier.rb | 22 ++++++++++ app/models/user.rb | 1 + app/serializers/user_info_serializer.rb | 10 ++++- ...150613202109_create_o_auth_applications.rb | 5 ++- db/migrate/20150708154727_create_scope.rb | 2 +- ...reate_pairwise_pseudonymous_identifiers.rb | 11 +++++ db/schema.rb | 44 ++++++++++++------- features/step_definitions/auth_code_steps.rb | 4 ++ lib/account_deleter.rb | 2 +- .../openid_connect/clients_controller_spec.rb | 15 ++++--- .../discovery_controller_spec.rb | 9 +++- ...r_spec.rb => user_info_controller_spec.rb} | 9 +++- .../api/openid_connect/token_endpoint_spec.rb | 5 ++- 17 files changed, 137 insertions(+), 50 deletions(-) create mode 100644 app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb create mode 100644 db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb rename spec/integration/api/{users_controller_spec.rb => user_info_controller_spec.rb} (68%) diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index 15ce38738..7c92ecbd7 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -18,7 +18,7 @@ module Api registration_endpoint: api_openid_connect_clients_url, authorization_endpoint: new_api_openid_connect_authorization_url, token_endpoint: api_openid_connect_access_tokens_url, - userinfo_endpoint: api_v0_user_url, + userinfo_endpoint: api_openid_connect_user_info_url, jwks_uri: File.join(root_url, "api", "openid_connect", "jwks.json"), scopes_supported: Api::OpenidConnect::Scope.pluck(:name), response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types, diff --git a/app/controllers/api/openid_connect/user_info_controller.rb b/app/controllers/api/openid_connect/user_info_controller.rb index 9fb79226d..62de2389b 100644 --- a/app/controllers/api/openid_connect/user_info_controller.rb +++ b/app/controllers/api/openid_connect/user_info_controller.rb @@ -8,7 +8,7 @@ module Api end def show - render json: current_user, serializer: UserInfoSerializer + render json: current_user, serializer: UserInfoSerializer, authorization: current_token.authorization end def current_user diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb index 15becc1f8..2855a38b0 100644 --- a/app/models/api/openid_connect/id_token.rb +++ b/app/models/api/openid_connect/id_token.rb @@ -23,19 +23,29 @@ module Api end def claims + sub = build_sub @claims ||= { iss: AppConfig.environment.url, - # TODO: Convert to proper PPID - sub: "#{AppConfig.environment.url}#{authorization.o_auth_application.client_id}#{authorization.user.id}", + sub: sub, aud: authorization.o_auth_application.client_id, exp: expires_at.to_i, iat: created_at.to_i, auth_time: authorization.user.current_sign_in_at.to_i, - nonce: nonce, - acr: 0 # TODO: Adjust ? + nonce: nonce } end + def build_sub + if authorization.o_auth_application.ppid? + sector_identifier = authorization.o_auth_application.sector_identifier_uri + pairwise_pseudonymous_identifier = + authorization.user.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: sector_identifier) + pairwise_pseudonymous_identifier.guid + else + authorization.user.diaspora_handle + end + end + # TODO: Add support for request objects end end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 3cc2dadbc..5ecf8ea69 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -8,24 +8,15 @@ module Api validates :client_secret, presence: true validates :client_name, presence: true - serialize :redirect_uris, JSON - serialize :response_types, JSON - serialize :grant_types, JSON - serialize :contacts, JSON + %i(redirect_uris response_types grant_types contacts).each do |serializable| + serialize serializable, JSON + end before_validation :setup, on: :create def setup self.client_id = SecureRandom.hex(16) self.client_secret = SecureRandom.hex(32) - self.response_types = [] - self.grant_types = [] - self.application_type = "web" - self.contacts = [] - self.logo_uri = "" - self.client_uri = "" - self.policy_uri = "" - self.tos_uri = "" end class << self @@ -46,12 +37,19 @@ module Api def supported_metadata %i(client_name response_types grant_types application_type - contacts logo_uri client_uri policy_uri tos_uri redirect_uris) + contacts logo_uri client_uri policy_uri tos_uri redirect_uris + sector_identifier_uri subject_type) end def registrar_attributes(registrar) supported_metadata.each_with_object({}) do |key, attr| - attr[key] = registrar.public_send(key) if registrar.public_send(key) + value = registrar.public_send(key) + next unless value + if key == :subject_type + attr[:ppid] = (value == "pairwise") + else + attr[key] = value + end end end end diff --git a/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb b/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb new file mode 100644 index 000000000..06c115daf --- /dev/null +++ b/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb @@ -0,0 +1,22 @@ +module Api + module OpenidConnect + class PairwisePseudonymousIdentifier < ActiveRecord::Base + self.table_name = "ppid" + + belongs_to :o_auth_application + belongs_to :user + + validates :user, presence: true + validates :sector_identifier, presence: true, uniqueness: {scope: :user} + validates :guid, presence: true, uniqueness: true + + before_validation :setup, on: :create + + private + + def setup + self.guid = SecureRandom.hex(16) + end + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index a7b58882c..ec009aa47 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -76,6 +76,7 @@ class User < ActiveRecord::Base has_many :reports + has_many :pairwise_pseudonymous_identifiers, class_name: "Api::OpenidConnect::PairwisePseudonymousIdentifier" has_many :authorizations, class_name: "Api::OpenidConnect::Authorization" has_many :o_auth_applications, through: :authorizations, class_name: "Api::OpenidConnect::OAuthApplication" diff --git a/app/serializers/user_info_serializer.rb b/app/serializers/user_info_serializer.rb index 3f042380d..67cf3db11 100644 --- a/app/serializers/user_info_serializer.rb +++ b/app/serializers/user_info_serializer.rb @@ -2,7 +2,15 @@ class UserInfoSerializer < ActiveModel::Serializer attributes :sub, :nickname, :profile, :picture, :zoneinfo def sub - object.diaspora_handle # TODO: Change to proper sub + auth = serialization_options[:authorization] + if auth.o_auth_application.ppid? + sector_identifier = auth.o_auth_application.sector_identifier_uri + pairwise_pseudonymous_identifier = + object.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: sector_identifier) + pairwise_pseudonymous_identifier.guid + else + object.diaspora_handle + end end def nickname diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index e9452a6c7..874122eb2 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -5,15 +5,18 @@ class CreateOAuthApplications < ActiveRecord::Migration t.string :client_id t.string :client_secret t.string :client_name + t.string :redirect_uris t.string :response_types t.string :grant_types - t.string :application_type + t.string :application_type, default: "web" t.string :contacts t.string :logo_uri t.string :client_uri t.string :policy_uri t.string :tos_uri + t.string :sector_identifier_uri + t.boolean :ppid, default: false t.timestamps null: false end diff --git a/db/migrate/20150708154727_create_scope.rb b/db/migrate/20150708154727_create_scope.rb index 8d2c51c02..afdc320f6 100644 --- a/db/migrate/20150708154727_create_scope.rb +++ b/db/migrate/20150708154727_create_scope.rb @@ -1,7 +1,7 @@ class CreateScope < ActiveRecord::Migration def change create_table :scopes do |t| - t.string :name + t.primary_key :name, :string t.timestamps null: false end diff --git a/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb b/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb new file mode 100644 index 000000000..125b95bfc --- /dev/null +++ b/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb @@ -0,0 +1,11 @@ +class CreatePairwisePseudonymousIdentifiers < ActiveRecord::Migration + def change + create_table :ppid do |t| + t.belongs_to :o_auth_application, index: true + t.belongs_to :user, index: true + + t.primary_key :guid, :string, limit: 32 + t.string :sector_identifier + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 21bb99e43..09519a9e4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150724152052) do +ActiveRecord::Schema.define(version: 20150801074555) do create_table "account_deletions", force: :cascade do |t| t.string "diaspora_handle", limit: 255 @@ -278,21 +278,23 @@ ActiveRecord::Schema.define(version: 20150724152052) do add_index "o_auth_access_tokens", ["authorization_id"], name: "index_o_auth_access_tokens_on_authorization_id", using: :btree create_table "o_auth_applications", force: :cascade do |t| - t.integer "user_id", limit: 4 - t.string "client_id", limit: 255 - t.string "client_secret", limit: 255 - t.string "client_name", limit: 255 - t.string "redirect_uris", limit: 255 - t.string "response_types", limit: 255 - t.string "grant_types", limit: 255 - t.string "application_type", limit: 255 - t.string "contacts", limit: 255 - t.string "logo_uri", limit: 255 - t.string "client_uri", limit: 255 - t.string "policy_uri", limit: 255 - t.string "tos_uri", limit: 255 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "user_id", limit: 4 + t.string "client_id", limit: 255 + t.string "client_secret", limit: 255 + t.string "client_name", limit: 255 + t.string "redirect_uris", limit: 255 + t.string "response_types", limit: 255 + t.string "grant_types", limit: 255 + t.string "application_type", limit: 255, default: "web" + t.string "contacts", limit: 255 + t.string "logo_uri", limit: 255 + t.string "client_uri", limit: 255 + t.string "policy_uri", limit: 255 + t.string "tos_uri", limit: 255 + t.string "sector_identifier_uri", limit: 255 + t.boolean "ppid", default: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "o_auth_applications", ["user_id"], name: "index_o_auth_applications_on_user_id", using: :btree @@ -463,6 +465,16 @@ ActiveRecord::Schema.define(version: 20150724152052) do add_index "posts", ["tweet_id"], name: "index_posts_on_tweet_id", length: {"tweet_id"=>191}, using: :btree add_index "posts", ["type", "pending", "id"], name: "index_posts_on_type_and_pending_and_id", using: :btree + create_table "ppid", force: :cascade do |t| + t.integer "o_auth_application_id", limit: 4 + t.integer "user_id", limit: 4 + t.string "guid", limit: 32 + t.string "sector_identifier", limit: 255 + end + + add_index "ppid", ["o_auth_application_id"], name: "index_ppid_on_o_auth_application_id", using: :btree + add_index "ppid", ["user_id"], name: "index_ppid_on_user_id", using: :btree + create_table "profiles", force: :cascade do |t| t.string "diaspora_handle", limit: 255 t.string "first_name", limit: 127 diff --git a/features/step_definitions/auth_code_steps.rb b/features/step_definitions/auth_code_steps.rb index a34c284d9..92a1c93d8 100644 --- a/features/step_definitions/auth_code_steps.rb +++ b/features/step_definitions/auth_code_steps.rb @@ -28,5 +28,9 @@ end When /^I parse the tokens and use it obtain user info$/ do client_json = JSON.parse(last_response.body) access_token = client_json["access_token"] + encoded_id_token = client_json["id_token"] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, + Api::OpenidConnect::IdTokenConfig.public_key + expect(decoded_token.sub).to eq(@me.diaspora_handle) get api_openid_connect_user_info_path, access_token: access_token end diff --git a/lib/account_deleter.rb b/lib/account_deleter.rb index 10dee342e..4d0335100 100644 --- a/lib/account_deleter.rb +++ b/lib/account_deleter.rb @@ -47,7 +47,7 @@ class AccountDeleter #user deletions def normal_ar_user_associates_to_delete %i(tag_followings invitations_to_me services aspects user_preferences - notifications blocks authorizations o_auth_applications) + notifications blocks authorizations o_auth_applications pairwise_pseudonymous_identifiers) end def special_ar_user_associations diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index 5326fa32d..e685aeb47 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -6,17 +6,19 @@ describe Api::OpenidConnect::ClientsController, type: :controller do it "should return a client id" do post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], - logo_uri: "http://test.com/logo.png", client_uri: "http://test.com/client", - policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos" + logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client", + policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos", + sector_identifier_uri: "http://example.com/uris", subject_type: "pairwise" client_json = JSON.parse(response.body) expect(client_json["o_auth_application"]["client_id"].length).to eq(32) + expect(client_json["o_auth_application"]["ppid"]).to eq(true) end end context "when redirect uri is missing" do it "should return a invalid_client_metadata error" do post :create, response_types: [], grant_types: [], application_type: "web", contacts: [], - logo_uri: "http://test.com/logo.png", client_uri: "http://test.com/client", - policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos" + logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client", + policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos" client_json = JSON.parse(response.body) expect(client_json["error"]).to have_content("invalid_client_metadata") end @@ -24,8 +26,9 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when redirect client_name is missing" do it "should return a invalid_client_metadata error" do post :create, redirect_uris: ["http://localhost"], response_types: [], grant_types: [], - application_type: "web", contacts: [], logo_uri: "http://test.com/logo.png", - client_uri: "http://test.com/client", policy_uri: "http://test.com/policy", tos_uri: "http://test.com/tos" + application_type: "web", contacts: [], logo_uri: "http://example.com/logo.png", + client_uri: "http://example.com/client", policy_uri: "http://example.com/policy", + tos_uri: "http://example.com/tos" client_json = JSON.parse(response.body) expect(client_json["error"]).to have_content("invalid_client_metadata") end diff --git a/spec/controllers/api/openid_connect/discovery_controller_spec.rb b/spec/controllers/api/openid_connect/discovery_controller_spec.rb index f71126462..0233ae180 100644 --- a/spec/controllers/api/openid_connect/discovery_controller_spec.rb +++ b/spec/controllers/api/openid_connect/discovery_controller_spec.rb @@ -18,10 +18,17 @@ describe Api::OpenidConnect::DiscoveryController, type: :controller do end describe "#configuration" do - it "should have the issuer as the root url" do + before do get :configuration + end + it "should have the issuer as the root url" do json_body = JSON.parse(response.body) expect(json_body["issuer"]).to eq("http://test.host/") end + + it "should have the appropriate user info endpoint" do + json_body = JSON.parse(response.body) + expect(json_body["userinfo_endpoint"]).to eq(api_openid_connect_user_info_url) + end end end diff --git a/spec/integration/api/users_controller_spec.rb b/spec/integration/api/user_info_controller_spec.rb similarity index 68% rename from spec/integration/api/users_controller_spec.rb rename to spec/integration/api/user_info_controller_spec.rb index 7b13f738f..ad47b486b 100644 --- a/spec/integration/api/users_controller_spec.rb +++ b/spec/integration/api/user_info_controller_spec.rb @@ -1,10 +1,12 @@ require "spec_helper" -describe Api::V0::UsersController do +describe Api::OpenidConnect::UserInfoController do # TODO: Replace with factory let!(:client) do Api::OpenidConnect::OAuthApplication.create!( - client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) + client_name: "Diaspora Test Client", + redirect_uris: ["http://localhost:3000/"], + ppid: true, sector_identifier_uri: "https://example.com/uri") end let(:auth_with_read) do auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) @@ -21,6 +23,9 @@ describe Api::V0::UsersController do it "shows the info" do json_body = JSON.parse(response.body) + expected_sub = + alice.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: "https://example.com/uri").guid + expect(json_body["sub"]).to eq(expected_sub) expect(json_body["nickname"]).to eq(alice.name) expect(json_body["profile"]).to eq(File.join(AppConfig.environment.url, "people", alice.guid).to_s) end diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index 69228bcaf..d712327d0 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -3,7 +3,8 @@ require "spec_helper" describe Api::OpenidConnect::TokenEndpoint, type: :request do let!(:client) do Api::OpenidConnect::OAuthApplication.create!( - redirect_uris: ["http://localhost:3000/"], client_name: "diaspora client") + redirect_uris: ["http://localhost:3000/"], client_name: "diaspora client", + ppid: true, sector_identifier_uri: "https://example.com/uri") end let!(:auth) { Api::OpenidConnect::Authorization.find_or_create_by( @@ -28,6 +29,8 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do encoded_id_token = json["id_token"] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, Api::OpenidConnect::IdTokenConfig.public_key + expected_guid = bob.pairwise_pseudonymous_identifiers.find_by(sector_identifier: "https://example.com/uri").guid + expect(decoded_token.sub).to eq(expected_guid) expect(decoded_token.exp).to be > Time.zone.now.utc.to_i end From 65c40f236eea7f33807421ea56ac30a618b180d2 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sat, 1 Aug 2015 19:21:51 +0900 Subject: [PATCH 035/105] Load scopes from seeds Signed-off-by: theworldbright --- app/models/api/openid_connect/scope.rb | 2 -- db/seeds.rb | 3 +++ lib/api/openid_connect/token_endpoint.rb | 2 +- .../authorizations_controller_spec.rb | 1 - .../protected_resource_endpoint_spec.rb | 4 ++-- spec/lib/api/openid_connect/token_endpoint_spec.rb | 13 +++++-------- spec/spec_helper.rb | 1 + 7 files changed, 12 insertions(+), 14 deletions(-) create mode 100644 db/seeds.rb diff --git a/app/models/api/openid_connect/scope.rb b/app/models/api/openid_connect/scope.rb index 7b7d66ac1..aaf4794bf 100644 --- a/app/models/api/openid_connect/scope.rb +++ b/app/models/api/openid_connect/scope.rb @@ -4,8 +4,6 @@ module Api has_many :authorizations, through: :authorization_scopes validates :name, presence: true, uniqueness: true - - # TODO: Add constants so scopes can be referenced as OpenidConnect::Scope::Read end end end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 000000000..6ca70e345 --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,3 @@ +Api::OpenidConnect::Scope.find_or_create_by!(name: "openid") +Api::OpenidConnect::Scope.find_or_create_by!(name: "read") +Api::OpenidConnect::Scope.find_or_create_by!(name: "write") diff --git a/lib/api/openid_connect/token_endpoint.rb b/lib/api/openid_connect/token_endpoint.rb index 1f592c03c..86d8fed09 100644 --- a/lib/api/openid_connect/token_endpoint.rb +++ b/lib/api/openid_connect/token_endpoint.rb @@ -23,7 +23,7 @@ module Api auth = Api::OpenidConnect::Authorization.with_redirect_uri(req.redirect_uri).use_code(req.code) req.invalid_grant! if auth.blank? res.access_token = auth.create_access_token - if auth.accessible?(Api::OpenidConnect::Scope.find_by(name: "openid")) + if auth.accessible?(Api::OpenidConnect::Scope.find_by!(name: "openid")) id_token = auth.create_id_token res.id_token = id_token.to_jwt(access_token: res.access_token) end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 6663f281e..74aa1ed02 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -15,7 +15,6 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do before do sign_in :user, alice allow(@controller).to receive(:current_user).and_return(alice) - Api::OpenidConnect::Scope.create!(name: "openid") end describe "#new" do diff --git a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb index 219cf1c40..7cf0ccd29 100644 --- a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb @@ -8,8 +8,8 @@ describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do end let(:auth_with_read) do auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) - auth.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "openid"), - Api::OpenidConnect::Scope.find_or_create_by(name: "read")] + auth.scopes << [Api::OpenidConnect::Scope.find_by!(name: "openid"), + Api::OpenidConnect::Scope.find_by!(name: "read")] auth end let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index d712327d0..c0b06b5be 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -1,20 +1,17 @@ require "spec_helper" - describe Api::OpenidConnect::TokenEndpoint, type: :request do let!(:client) do Api::OpenidConnect::OAuthApplication.create!( redirect_uris: ["http://localhost:3000/"], client_name: "diaspora client", ppid: true, sector_identifier_uri: "https://example.com/uri") end - let!(:auth) { - Api::OpenidConnect::Authorization.find_or_create_by( + let!(:auth) do + auth = Api::OpenidConnect::Authorization.find_or_create_by( o_auth_application: client, user: bob, redirect_uri: "http://localhost:3000/") - } - let!(:code) { auth.create_code } - - before do - Api::OpenidConnect::Scope.find_or_create_by(name: "read") + auth.scopes << [Api::OpenidConnect::Scope.find_by!(name: "openid")] + auth end + let!(:code) { auth.create_code } describe "the authorization code grant type" do context "when the authorization code is valid" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ca444256a..c0a67974d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -84,6 +84,7 @@ RSpec.configure do |config| $process_queue = false allow_any_instance_of(Postzord::Dispatcher::Public).to receive(:deliver_to_remote) allow_any_instance_of(Postzord::Dispatcher::Private).to receive(:deliver_to_remote) + load "#{Rails.root}/db/seeds.rb" end config.expect_with :rspec do |expect_config| From 308170f691df1ef175fe72333956028ccc31d5e4 Mon Sep 17 00:00:00 2001 From: augier Date: Sat, 1 Aug 2015 15:27:40 +0200 Subject: [PATCH 036/105] Add applications information page --- .../user_applications_controller.rb | 9 +++++ .../api/openid_connect/o_auth_application.rb | 4 +++ app/presenters/user_applications_presenter.rb | 34 +++++++++++++++++++ app/views/shared/_settings_nav.haml | 1 + .../_add_remove_applications.haml | 21 ++++++++++++ app/views/user_applications/show.html.haml | 17 ++++++++++ config/locales/diaspora/en.yml | 9 +++++ config/routes.rb | 2 ++ 8 files changed, 97 insertions(+) create mode 100644 app/controllers/user_applications_controller.rb create mode 100644 app/presenters/user_applications_presenter.rb create mode 100644 app/views/user_applications/_add_remove_applications.haml create mode 100644 app/views/user_applications/show.html.haml diff --git a/app/controllers/user_applications_controller.rb b/app/controllers/user_applications_controller.rb new file mode 100644 index 000000000..b85b0f948 --- /dev/null +++ b/app/controllers/user_applications_controller.rb @@ -0,0 +1,9 @@ +class UserApplicationsController < ApplicationController + before_action :authenticate_user! + + def show + respond_to do |format| + format.all { @user_apps = UserApplicationsPresenter.new current_user } + end + end +end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 5ecf8ea69..87ad8e898 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -19,6 +19,10 @@ module Api self.client_secret = SecureRandom.hex(32) end + def image_uri + self.logo_uri ? self.logo_uri : "branding/logos/asterisk.png" + end + class << self def available_response_types ["id_token", "id_token token", "code"] diff --git a/app/presenters/user_applications_presenter.rb b/app/presenters/user_applications_presenter.rb new file mode 100644 index 000000000..59a407c46 --- /dev/null +++ b/app/presenters/user_applications_presenter.rb @@ -0,0 +1,34 @@ +class UserApplicationsPresenter + def initialize(user) + @current_user = user + end + + def user_applications + @applications ||= @current_user.o_auth_applications.each_with_object([]) do |app, array| + array << app_as_json(app) + end + end + + def applications_count + user_applications.size + end + + def applications? + applications_count > 0 + end + + private + + def app_as_json(application) + { + name: application.client_name, + image: application.image_uri, + autorizations: find_scopes(application) + } + end + + def find_scopes(application) + Api::OpenidConnect::Authorization.find_by_client_id_and_user( + application.client_id, @current_user).scopes + end +end diff --git a/app/views/shared/_settings_nav.haml b/app/views/shared/_settings_nav.haml index edaa2fcf5..8bd78cc53 100644 --- a/app/views/shared/_settings_nav.haml +++ b/app/views/shared/_settings_nav.haml @@ -6,3 +6,4 @@ %li{class: current_page?(edit_user_path) && "active"}= link_to t("account"), edit_user_path %li{class: current_page?(privacy_settings_path) && "active"}= link_to t("privacy"), privacy_settings_path %li{class: current_page?(services_path) && "active"}= link_to t("_services"), services_path + %li{class: current_page?(user_applications_path) && 'active'}= link_to t("_applications"), user_applications_path diff --git a/app/views/user_applications/_add_remove_applications.haml b/app/views/user_applications/_add_remove_applications.haml new file mode 100644 index 000000000..72efc3fbe --- /dev/null +++ b/app/views/user_applications/_add_remove_applications.haml @@ -0,0 +1,21 @@ +- if @user_apps.applications? + %ul.list-group + - @user_apps.user_applications.each do |app| + %li.list-group-item + .row + .col-xs-2 + = image_tag app[:image], class: "img-responsive" + .col-xs-10 + - if app[:autorizations].count > 0 + %h4="#{app[:name]} #{t("user_applications.show.access")}" + %ul + - app[:autorizations].each do |autorization| + %li=autorization.name + - else + .well + =t("user_applications.show.no_requirement") + +- else + .well + %h4 + = t("user_applications.show.no_applications") diff --git a/app/views/user_applications/show.html.haml b/app/views/user_applications/show.html.haml new file mode 100644 index 000000000..aa0a06527 --- /dev/null +++ b/app/views/user_applications/show.html.haml @@ -0,0 +1,17 @@ +- content_for :page_title do + = t(".edit_applications") + +.container-fluid + = render "shared/settings_nav" + .container-fluid + .row + .col-lg-8.col-lg-offset-2 + %h3= t(".title") + %p.visible-sm-block.visible-xs-block + = t(".applications_explanation") + .row + .col-md-7 + = render "add_remove_applications" + .col-md-5 + %p.hidden-sm.hidden-xs + = t(".applications_explanation") diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index e53b386c3..0f327c204 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -1476,3 +1476,12 @@ en: disabled: "Not available" open: "Open" closed: "Closed" + + user_applications: + show: + edit_applications: "Applications" + title: "Your installed applications" + no_applications: "You have no authorized application for now" + access: "is authorized to access to:" + no_requirement: "This application requires no autorizations" + applications_explanation: "Here are listed the applications to which you autorized the access to your profile informations" diff --git a/config/routes.rb b/config/routes.rb index 683e1da17..eb6054f70 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -254,4 +254,6 @@ Diaspora::Application.routes.draw do get "user_info", to: "user_info#show" end end + + resource :user_applications, only: [:show] end From dd337d416311179cead0590c2d367c3d4f9f2a46 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 2 Aug 2015 01:11:24 +0900 Subject: [PATCH 037/105] Remove JSON root from client controller Signed-off-by: theworldbright --- app/controllers/api/openid_connect/clients_controller.rb | 2 +- features/step_definitions/auth_code_steps.rb | 4 ++-- features/step_definitions/implicit_flow_steps.rb | 2 +- .../controllers/api/openid_connect/clients_controller_spec.rb | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/openid_connect/clients_controller.rb b/app/controllers/api/openid_connect/clients_controller.rb index d122a1692..2ba6efdbe 100644 --- a/app/controllers/api/openid_connect/clients_controller.rb +++ b/app/controllers/api/openid_connect/clients_controller.rb @@ -12,7 +12,7 @@ module Api def create registrar = OpenIDConnect::Client::Registrar.new(request.url, params) client = Api::OpenidConnect::OAuthApplication.register! registrar - render json: client + render json: client.as_json(root: false) end private diff --git a/features/step_definitions/auth_code_steps.rb b/features/step_definitions/auth_code_steps.rb index 92a1c93d8..36ea99ced 100644 --- a/features/step_definitions/auth_code_steps.rb +++ b/features/step_definitions/auth_code_steps.rb @@ -8,8 +8,8 @@ o_auth_query_params = %i( Given /^I send a post request from that client to the code flow authorization endpoint$/ do client_json = JSON.parse(last_response.body) - @client_id = client_json['o_auth_application']['client_id'] - @client_secret = client_json['o_auth_application']['client_secret'] + @client_id = client_json["client_id"] + @client_secret = client_json["client_secret"] visit new_api_openid_connect_authorization_path + "?client_id=#{@client_id}&#{o_auth_query_params}" end diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb index cb7394da1..cfeb61009 100644 --- a/features/step_definitions/implicit_flow_steps.rb +++ b/features/step_definitions/implicit_flow_steps.rb @@ -9,7 +9,7 @@ o_auth_query_params = %i( Given /^I send a post request from that client to the implicit flow authorization endpoint$/ do client_json = JSON.parse(last_response.body) visit new_api_openid_connect_authorization_path + - "?client_id=#{client_json['o_auth_application']['client_id']}&#{o_auth_query_params}" + "?client_id=#{client_json["client_id"]}&#{o_auth_query_params}" end Given /^I send a post request from that client to the implicit flow authorization endpoint using a invalid client id/ do diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index e685aeb47..48204c86b 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -10,8 +10,8 @@ describe Api::OpenidConnect::ClientsController, type: :controller do policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos", sector_identifier_uri: "http://example.com/uris", subject_type: "pairwise" client_json = JSON.parse(response.body) - expect(client_json["o_auth_application"]["client_id"].length).to eq(32) - expect(client_json["o_auth_application"]["ppid"]).to eq(true) + expect(client_json["client_id"].length).to eq(32) + expect(client_json["ppid"]).to eq(true) end end context "when redirect uri is missing" do From b9da104b28aa16a0269193ffe60ba12f38d2d6fe Mon Sep 17 00:00:00 2001 From: augier Date: Sat, 1 Aug 2015 18:29:53 +0200 Subject: [PATCH 038/105] Revoke button on applications page --- app/assets/stylesheets/mobile/settings.scss | 4 ++++ .../openid_connect/authorizations_controller.rb | 10 ++++++++++ app/presenters/user_applications_presenter.rb | 6 ++++++ app/views/shared/_settings_nav.mobile.haml | 1 + .../_add_remove_applications.haml | 9 +++++++-- app/views/user_applications/show.html.haml | 2 +- app/views/user_applications/show.mobile.haml | 14 ++++++++++++++ config/locales/diaspora/en.yml | 3 ++- config/routes.rb | 2 +- 9 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 app/views/user_applications/show.mobile.haml diff --git a/app/assets/stylesheets/mobile/settings.scss b/app/assets/stylesheets/mobile/settings.scss index 846d5159f..04662edcd 100644 --- a/app/assets/stylesheets/mobile/settings.scss +++ b/app/assets/stylesheets/mobile/settings.scss @@ -38,3 +38,7 @@ .info { color: $text; } } } + +.applications-page { + .application-img { margin: 9px 0; } +} \ No newline at end of file diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 16509f555..a8ca51c09 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -17,6 +17,16 @@ module Api process_authorization_consent(params[:approve]) end + def destroy + # TODO: Specs + begin + Api::OpenidConnect::Authorization.find_by(id: params[:id]).destroy + rescue + # TODO: Log something here? + end + redirect_to user_applications_url + end + private def request_authorization_consent_form # TODO: Add support for prompt params diff --git a/app/presenters/user_applications_presenter.rb b/app/presenters/user_applications_presenter.rb index 59a407c46..07f33e470 100644 --- a/app/presenters/user_applications_presenter.rb +++ b/app/presenters/user_applications_presenter.rb @@ -21,6 +21,7 @@ class UserApplicationsPresenter def app_as_json(application) { + id: find_id(application), name: application.client_name, image: application.image_uri, autorizations: find_scopes(application) @@ -31,4 +32,9 @@ class UserApplicationsPresenter Api::OpenidConnect::Authorization.find_by_client_id_and_user( application.client_id, @current_user).scopes end + + def find_id(application) + Api::OpenidConnect::Authorization.find_by_client_id_and_user( + application.client_id, @current_user).id + end end diff --git a/app/views/shared/_settings_nav.mobile.haml b/app/views/shared/_settings_nav.mobile.haml index e286c6b04..eff5ef101 100644 --- a/app/views/shared/_settings_nav.mobile.haml +++ b/app/views/shared/_settings_nav.mobile.haml @@ -6,3 +6,4 @@ %li= link_to_unless_current t('account'), edit_user_path %li= link_to_unless_current t('privacy'), privacy_settings_path %li= link_to_unless_current t('_services'), services_path + %li= link_to_unless_current t('_applications'), user_applications_path diff --git a/app/views/user_applications/_add_remove_applications.haml b/app/views/user_applications/_add_remove_applications.haml index 72efc3fbe..0a8fc7888 100644 --- a/app/views/user_applications/_add_remove_applications.haml +++ b/app/views/user_applications/_add_remove_applications.haml @@ -3,7 +3,7 @@ - @user_apps.user_applications.each do |app| %li.list-group-item .row - .col-xs-2 + .col-xs-2.application-img = image_tag app[:image], class: "img-responsive" .col-xs-10 - if app[:autorizations].count > 0 @@ -14,8 +14,13 @@ - else .well =t("user_applications.show.no_requirement") + .small-horizontal-spacer + .row + = form_for "application", url: "#{api_openid_connect_authorizations_path}/#{app[:id]}", + html: { method: :delete, class: "form-horizontal col-xs-12"} do |f| + .clearfix= f.submit t("user_applications.revoke_autorization"), class: "btn btn-primary pull-right" - else .well %h4 - = t("user_applications.show.no_applications") + = t("user_applications.no_applications") diff --git a/app/views/user_applications/show.html.haml b/app/views/user_applications/show.html.haml index aa0a06527..07ebf6a24 100644 --- a/app/views/user_applications/show.html.haml +++ b/app/views/user_applications/show.html.haml @@ -1,7 +1,7 @@ - content_for :page_title do = t(".edit_applications") -.container-fluid +.container-fluid.applications-page = render "shared/settings_nav" .container-fluid .row diff --git a/app/views/user_applications/show.mobile.haml b/app/views/user_applications/show.mobile.haml new file mode 100644 index 000000000..696ce47d8 --- /dev/null +++ b/app/views/user_applications/show.mobile.haml @@ -0,0 +1,14 @@ +.settings_container.applications-page + - content_for :page_title do + = t('.edit_applications') + + = render 'shared/settings_nav' + + .container-fluid + .row + .col-md-12 + = t('.applications_explanation') + .small-horizontal-spacer + .col-md-12 + = render 'add_remove_applications' + diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 0f327c204..92528ba8e 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -1481,7 +1481,8 @@ en: show: edit_applications: "Applications" title: "Your installed applications" - no_applications: "You have no authorized application for now" access: "is authorized to access to:" no_requirement: "This application requires no autorizations" applications_explanation: "Here are listed the applications to which you autorized the access to your profile informations" + no_applications: "You have no authorized application for now" + revoke_autorization: "Revoke autorization" diff --git a/config/routes.rb b/config/routes.rb index eb6054f70..9a8f841bc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -244,7 +244,7 @@ Diaspora::Application.routes.draw do # Authorization Servers MUST support the use of the HTTP GET and POST methods at the Authorization Endpoint # See http://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation - resources :authorizations, only: %i(new create) + resources :authorizations, only: %i(new create destroy) post "authorizations/new", to: "authorizations#new" get ".well-known/webfinger", to: "discovery#webfinger" From 6e1a67345990631f25e60434181be9f650c44a98 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 2 Aug 2015 13:07:04 +0900 Subject: [PATCH 039/105] Replace let!() with factory girl --- .../authorizations_controller_spec.rb | 12 +---- spec/factories.rb | 48 +++++++++++++++++++ .../api/user_info_controller_spec.rb | 24 +++------- .../protected_resource_endpoint_spec.rb | 12 +---- .../api/openid_connect/token_endpoint_spec.rb | 6 +-- 5 files changed, 58 insertions(+), 44 deletions(-) diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 74aa1ed02..d60ec9c1e 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -1,16 +1,8 @@ require "spec_helper" describe Api::OpenidConnect::AuthorizationsController, type: :controller do - let!(:client) do - Api::OpenidConnect::OAuthApplication.create!( - client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) - end - let!(:client_with_multiple_redirects) do - Api::OpenidConnect::OAuthApplication.create!( - client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/", "http://localhost/"]) - end - - # TODO: jhass - "Might want to setup some factories in spec/factories.rb, see factory_girl's docs." + let!(:client) { FactoryGirl.create(:o_auth_application) } + let!(:client_with_multiple_redirects) { FactoryGirl.create(:o_auth_application_with_multiple_redirects) } before do sign_in :user, alice diff --git a/spec/factories.rb b/spec/factories.rb index 9adf5534b..946985a95 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -310,6 +310,54 @@ FactoryGirl.define do factory(:status, :parent => :status_message) + factory :o_auth_application, class: Api::OpenidConnect::OAuthApplication do + client_name "Diaspora Test Client" + redirect_uris ["http://localhost:3000/"] + end + + factory :o_auth_application_with_ppid, class: Api::OpenidConnect::OAuthApplication do + client_name "Diaspora Test Client" + redirect_uris ["http://localhost:3000/"] + ppid true + sector_identifier_uri "https://example.com/uri" + end + + factory :o_auth_application_with_multiple_redirects, class: Api::OpenidConnect::OAuthApplication do + client_name "Diaspora Test Client" + redirect_uris ["http://localhost:3000/", "http://localhost/"] + end + + factory :auth_with_read, class: Api::OpenidConnect::Authorization do + o_auth_application + user + + after(:create) do |auth_with_read| + auth_with_read.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "openid"), + Api::OpenidConnect::Scope.find_or_create_by(name: "read")] + end + end + + factory :auth_with_read_and_ppid, class: Api::OpenidConnect::Authorization do + association :o_auth_application, factory: :o_auth_application_with_ppid + user + + after(:create) do |auth_with_read| + auth_with_read.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "openid"), + Api::OpenidConnect::Scope.find_or_create_by(name: "read")] + end + end + + factory :auth_with_read_and_write, class: Api::OpenidConnect::Authorization do + o_auth_application + user + + after(:create) do |auth_with_read| + auth_with_read.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "openid"), + Api::OpenidConnect::Scope.find_or_create_by(name: "read"), + Api::OpenidConnect::Scope.find_or_create_by(name: "write")] + end + end + # Factories for the DiasporaFederation-gem factory(:federation_person_from_webfinger, class: DiasporaFederation::Entities::Person) do diff --git a/spec/integration/api/user_info_controller_spec.rb b/spec/integration/api/user_info_controller_spec.rb index ad47b486b..dd4fb95fe 100644 --- a/spec/integration/api/user_info_controller_spec.rb +++ b/spec/integration/api/user_info_controller_spec.rb @@ -1,33 +1,21 @@ require "spec_helper" - describe Api::OpenidConnect::UserInfoController do - # TODO: Replace with factory - let!(:client) do - Api::OpenidConnect::OAuthApplication.create!( - client_name: "Diaspora Test Client", - redirect_uris: ["http://localhost:3000/"], - ppid: true, sector_identifier_uri: "https://example.com/uri") - end - let(:auth_with_read) do - auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) - auth.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "openid"), - Api::OpenidConnect::Scope.find_or_create_by(name: "read")] - auth - end - let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } + let!(:auth_with_read_and_ppid) { FactoryGirl.create(:auth_with_read_and_ppid) } + let!(:access_token_with_read) { auth_with_read_and_ppid.create_access_token.to_s } describe "#show" do before do + @user = auth_with_read_and_ppid.user get api_openid_connect_user_info_path, access_token: access_token_with_read end it "shows the info" do json_body = JSON.parse(response.body) expected_sub = - alice.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: "https://example.com/uri").guid + @user.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: "https://example.com/uri").guid expect(json_body["sub"]).to eq(expected_sub) - expect(json_body["nickname"]).to eq(alice.name) - expect(json_body["profile"]).to eq(File.join(AppConfig.environment.url, "people", alice.guid).to_s) + expect(json_body["nickname"]).to eq(@user.name) + expect(json_body["profile"]).to eq(File.join(AppConfig.environment.url, "people", @user.guid).to_s) end end end diff --git a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb index 7cf0ccd29..ec819f13e 100644 --- a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb @@ -1,17 +1,7 @@ require "spec_helper" describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do - # TODO: Replace with factory - let!(:client) do - Api::OpenidConnect::OAuthApplication.create!( - client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"]) - end - let(:auth_with_read) do - auth = Api::OpenidConnect::Authorization.create!(o_auth_application: client, user: alice) - auth.scopes << [Api::OpenidConnect::Scope.find_by!(name: "openid"), - Api::OpenidConnect::Scope.find_by!(name: "read")] - auth - end + let(:auth_with_read) { FactoryGirl.create(:auth_with_read) } let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } let(:invalid_token) { SecureRandom.hex(32).to_s } diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index c0b06b5be..1dad2c133 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -1,10 +1,6 @@ require "spec_helper" describe Api::OpenidConnect::TokenEndpoint, type: :request do - let!(:client) do - Api::OpenidConnect::OAuthApplication.create!( - redirect_uris: ["http://localhost:3000/"], client_name: "diaspora client", - ppid: true, sector_identifier_uri: "https://example.com/uri") - end + let!(:client) { FactoryGirl.create(:o_auth_application_with_ppid) } let!(:auth) do auth = Api::OpenidConnect::Authorization.find_or_create_by( o_auth_application: client, user: bob, redirect_uri: "http://localhost:3000/") From 98fd18077a950af84609c29bd9cc19b1d29a4746 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 2 Aug 2015 13:52:09 +0900 Subject: [PATCH 040/105] Add test for expired access token --- .../api/openid_connect/authorization.rb | 1 - .../authorization_point/endpoint.rb | 2 -- .../protected_resource_endpoint_spec.rb | 22 ++++++++++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index 90a225845..8ca7c8e41 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -57,7 +57,6 @@ module Api auth.code = nil if auth # Remove auth code if found so it can't be reused auth end - # TODO: Consider splitting into subclasses by flow type end end end diff --git a/lib/api/openid_connect/authorization_point/endpoint.rb b/lib/api/openid_connect/authorization_point/endpoint.rb index 29d010f91..38ccb5f99 100644 --- a/lib/api/openid_connect/authorization_point/endpoint.rb +++ b/lib/api/openid_connect/authorization_point/endpoint.rb @@ -50,8 +50,6 @@ module Api end } end - - # TODO: buildResponseType(req) end end end diff --git a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb index ec819f13e..0d1f9eaa3 100644 --- a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb @@ -1,12 +1,15 @@ require "spec_helper" - describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do let(:auth_with_read) { FactoryGirl.create(:auth_with_read) } let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } + let!(:expired_access_token) do + access_token = auth_with_read.o_auth_access_tokens.create! + access_token.expires_at = Time.zone.now - 100 + access_token.save + access_token.bearer_token.to_s + end let(:invalid_token) { SecureRandom.hex(32).to_s } - # TODO: Add tests for expired access tokens - context "when valid access token is provided" do before do get api_openid_connect_user_info_path, access_token: access_token_with_read @@ -17,6 +20,19 @@ describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do end end + context "when access token is expired" do + before do + get api_openid_connect_user_info_path, access_token: expired_access_token + end + + it "should respond with a 401 Unauthorized response" do + expect(response.status).to be(401) + end + it "should have an auth-scheme value of Bearer" do + expect(response.headers["WWW-Authenticate"]).to include("Bearer") + end + end + context "when no access token is provided" do before do get api_openid_connect_user_info_path From 3734e074a66a325a2ececf884e17d1c353bb6459 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 2 Aug 2015 14:29:02 +0900 Subject: [PATCH 041/105] Fix pronto errors --- .../api/openid_connect/authorizations_controller.rb | 6 +++++- app/models/api/openid_connect/authorization.rb | 2 +- app/models/api/openid_connect/o_auth_application.rb | 2 +- app/presenters/api/v0/base_presenter.rb | 6 +++++- config/application.rb | 3 ++- features/step_definitions/implicit_flow_steps.rb | 2 +- lib/api/openid_connect/token_endpoint.rb | 4 ++-- 7 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index a8ca51c09..3a57a0832 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -95,7 +95,7 @@ module Api end def restore_request_parameters - req = Rack::Request.new(request.env) + req = build_rack_request req.update_param("client_id", session[:client_id]) req.update_param("redirect_uri", session[:redirect_uri]) req.update_param("response_type", response_type_as_space_seperated_values) @@ -104,6 +104,10 @@ module Api req.update_param("nonce", session[:nonce]) end + def build_rack_request + Rack::Request.new(request.env) + end + def response_type_as_space_seperated_values if session[:response_type].respond_to?(:map) session[:response_type].map(&:to_s).join(" ") diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index 8ca7c8e41..639bf320a 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -30,7 +30,7 @@ module Api def create_code self.code = SecureRandom.hex(32) save - self.code + code end def create_access_token diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 87ad8e898..6589cdec4 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -20,7 +20,7 @@ module Api end def image_uri - self.logo_uri ? self.logo_uri : "branding/logos/asterisk.png" + logo_uri ? logo_uri : "branding/logos/asterisk.png" end class << self diff --git a/app/presenters/api/v0/base_presenter.rb b/app/presenters/api/v0/base_presenter.rb index b425b1440..18548cdd5 100644 --- a/app/presenters/api/v0/base_presenter.rb +++ b/app/presenters/api/v0/base_presenter.rb @@ -1,2 +1,6 @@ -class Api::V0::BasePresenter +module Api + module V0 + class BasePresenter + end + end end diff --git a/config/application.rb b/config/application.rb index e5d10d066..d623be188 100644 --- a/config/application.rb +++ b/config/application.rb @@ -109,7 +109,8 @@ module Diaspora config.action_mailer.asset_host = AppConfig.pod_uri.to_s config.middleware.use Rack::OAuth2::Server::Resource::Bearer, "OpenID Connect" do |req| - Api::OpenidConnect::OAuthAccessToken.valid(Time.zone.now.utc).find_by(token: req.access_token) || req.invalid_token! + Api::OpenidConnect::OAuthAccessToken + .valid(Time.zone.now.utc).find_by(token: req.access_token) || req.invalid_token! end end end diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb index cfeb61009..4516ab55d 100644 --- a/features/step_definitions/implicit_flow_steps.rb +++ b/features/step_definitions/implicit_flow_steps.rb @@ -9,7 +9,7 @@ o_auth_query_params = %i( Given /^I send a post request from that client to the implicit flow authorization endpoint$/ do client_json = JSON.parse(last_response.body) visit new_api_openid_connect_authorization_path + - "?client_id=#{client_json["client_id"]}&#{o_auth_query_params}" + "?client_id=#{client_json['client_id']}&#{o_auth_query_params}" end Given /^I send a post request from that client to the implicit flow authorization endpoint using a invalid client id/ do diff --git a/lib/api/openid_connect/token_endpoint.rb b/lib/api/openid_connect/token_endpoint.rb index 86d8fed09..d71525e87 100644 --- a/lib/api/openid_connect/token_endpoint.rb +++ b/lib/api/openid_connect/token_endpoint.rb @@ -8,14 +8,14 @@ module Api @app = Rack::OAuth2::Server::Token.new do |req, res| o_auth_app = retrieve_client(req) if app_valid?(o_auth_app, req) - handle_flows(o_auth_app, req, res) + handle_flows(req, res) else req.invalid_client! end end end - def handle_flows(o_auth_app, req, res) + def handle_flows(req, res) case req.grant_type when :refresh_token handle_refresh_flow(req, res) From 469521c572642ad113c93fb1763239d07e8dfd44 Mon Sep 17 00:00:00 2001 From: augier Date: Sun, 2 Aug 2015 11:10:31 +0200 Subject: [PATCH 042/105] Addin scopes translation and description --- .../user_applications_controller.rb | 2 +- app/presenters/user_applications_presenter.rb | 11 ++++++----- .../_add_remove_applications.haml | 10 ++++++---- .../{show.html.haml => index.html.haml} | 0 .../{show.mobile.haml => index.mobile.haml} | 0 config/locales/diaspora/en.yml | 18 ++++++++++++++++-- config/routes.rb | 2 +- 7 files changed, 30 insertions(+), 13 deletions(-) rename app/views/user_applications/{show.html.haml => index.html.haml} (100%) rename app/views/user_applications/{show.mobile.haml => index.mobile.haml} (100%) diff --git a/app/controllers/user_applications_controller.rb b/app/controllers/user_applications_controller.rb index b85b0f948..35741d02b 100644 --- a/app/controllers/user_applications_controller.rb +++ b/app/controllers/user_applications_controller.rb @@ -1,7 +1,7 @@ class UserApplicationsController < ApplicationController before_action :authenticate_user! - def show + def index respond_to do |format| format.all { @user_apps = UserApplicationsPresenter.new current_user } end diff --git a/app/presenters/user_applications_presenter.rb b/app/presenters/user_applications_presenter.rb index 07f33e470..5a0ad9cfc 100644 --- a/app/presenters/user_applications_presenter.rb +++ b/app/presenters/user_applications_presenter.rb @@ -21,16 +21,17 @@ class UserApplicationsPresenter def app_as_json(application) { - id: find_id(application), - name: application.client_name, - image: application.image_uri, - autorizations: find_scopes(application) + id: find_id(application), + name: application.client_name, + image: application.image_uri, + authorizations: find_scopes(application) } end def find_scopes(application) - Api::OpenidConnect::Authorization.find_by_client_id_and_user( + scopes = Api::OpenidConnect::Authorization.find_by_client_id_and_user( application.client_id, @current_user).scopes + scopes.each_with_object([]){|scope, array| array << scope.name } end def find_id(application) diff --git a/app/views/user_applications/_add_remove_applications.haml b/app/views/user_applications/_add_remove_applications.haml index 0a8fc7888..2a6a1e3f5 100644 --- a/app/views/user_applications/_add_remove_applications.haml +++ b/app/views/user_applications/_add_remove_applications.haml @@ -6,11 +6,13 @@ .col-xs-2.application-img = image_tag app[:image], class: "img-responsive" .col-xs-10 - - if app[:autorizations].count > 0 - %h4="#{app[:name]} #{t("user_applications.show.access")}" + - if app[:authorizations].count > 0 + %h4="#{app[:name]} #{t("user_applications.index.access")}" %ul - - app[:autorizations].each do |autorization| - %li=autorization.name + - app[:authorizations].each do |authorization| + %li + %b= t("user_applications.scopes.#{authorization}.name") + %p= t("user_applications.scopes.#{authorization}.description") - else .well =t("user_applications.show.no_requirement") diff --git a/app/views/user_applications/show.html.haml b/app/views/user_applications/index.html.haml similarity index 100% rename from app/views/user_applications/show.html.haml rename to app/views/user_applications/index.html.haml diff --git a/app/views/user_applications/show.mobile.haml b/app/views/user_applications/index.mobile.haml similarity index 100% rename from app/views/user_applications/show.mobile.haml rename to app/views/user_applications/index.mobile.haml diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 92528ba8e..83b4ebc34 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -1478,11 +1478,25 @@ en: closed: "Closed" user_applications: - show: + index: edit_applications: "Applications" - title: "Your installed applications" + title: "Your authorized applications" access: "is authorized to access to:" no_requirement: "This application requires no autorizations" applications_explanation: "Here are listed the applications to which you autorized the access to your profile informations" no_applications: "You have no authorized application for now" revoke_autorization: "Revoke autorization" + scopes: + openid: + name: "basic profile" + description: "This allows the application to read informations of your basic profile" + extended: + name: "extended profile" + description: "This allows the application to read informations of your extended profile" + read: + name: "read profile, stream and conversations" + description: "This allows the application to read your stream, your conversations and your complete profile" + write: + name: "send post, conversations and likes" + description: "This allows the application to send new posts, start conversations or send a new messages in an existant conversation and send likes" + diff --git a/config/routes.rb b/config/routes.rb index 9a8f841bc..b38cd165c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -255,5 +255,5 @@ Diaspora::Application.routes.draw do end end - resource :user_applications, only: [:show] + get "user_applications", to: "user_applications#index" end From 098e8e46dd015f6d1b2428dca933e6046c395909 Mon Sep 17 00:00:00 2001 From: augier Date: Sun, 2 Aug 2015 11:50:29 +0200 Subject: [PATCH 043/105] CSS for applications settings page --- app/assets/stylesheets/_application.scss | 4 +++ app/assets/stylesheets/mobile/settings.scss | 18 +++++++++- app/assets/stylesheets/user_applications.scss | 19 ++++++++++ .../_add_remove_applications.haml | 35 +++++++++---------- 4 files changed, 56 insertions(+), 20 deletions(-) create mode 100644 app/assets/stylesheets/user_applications.scss diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index 37405d3b0..514bcc153 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -101,3 +101,7 @@ /* gallery */ @import "blueimp-gallery"; @import "gallery"; + +/* settings */ +@import "user_applications"; +@import "bootstrap3-switch"; \ No newline at end of file diff --git a/app/assets/stylesheets/mobile/settings.scss b/app/assets/stylesheets/mobile/settings.scss index 04662edcd..a19241e60 100644 --- a/app/assets/stylesheets/mobile/settings.scss +++ b/app/assets/stylesheets/mobile/settings.scss @@ -40,5 +40,21 @@ } .applications-page { - .application-img { margin: 9px 0; } + .application-img { + display: inline; + float: left; + + img { + width: 70px; + max-height: 70px; + margin: 9px 0; + } + } + + .application-authorizations { + width: calc(100% - 70px); + padding: 0 0 15px 15px; + display: inline-block; + float: right; + } } \ No newline at end of file diff --git a/app/assets/stylesheets/user_applications.scss b/app/assets/stylesheets/user_applications.scss new file mode 100644 index 000000000..32cadd102 --- /dev/null +++ b/app/assets/stylesheets/user_applications.scss @@ -0,0 +1,19 @@ +.applications-page { + .application-img { + display: inline; + float: left; + + img { + width: 70px; + max-height: 70px; + margin: 9px 0; + } + } + + .application-authorizations { + width: calc(100% - 70px); + padding: 0 0 15px 15px; + display: inline-block; + float: right; + } +} diff --git a/app/views/user_applications/_add_remove_applications.haml b/app/views/user_applications/_add_remove_applications.haml index 2a6a1e3f5..4f0c082d0 100644 --- a/app/views/user_applications/_add_remove_applications.haml +++ b/app/views/user_applications/_add_remove_applications.haml @@ -2,25 +2,22 @@ %ul.list-group - @user_apps.user_applications.each do |app| %li.list-group-item - .row - .col-xs-2.application-img - = image_tag app[:image], class: "img-responsive" - .col-xs-10 - - if app[:authorizations].count > 0 - %h4="#{app[:name]} #{t("user_applications.index.access")}" - %ul - - app[:authorizations].each do |authorization| - %li - %b= t("user_applications.scopes.#{authorization}.name") - %p= t("user_applications.scopes.#{authorization}.description") - - else - .well - =t("user_applications.show.no_requirement") - .small-horizontal-spacer - .row - = form_for "application", url: "#{api_openid_connect_authorizations_path}/#{app[:id]}", - html: { method: :delete, class: "form-horizontal col-xs-12"} do |f| - .clearfix= f.submit t("user_applications.revoke_autorization"), class: "btn btn-primary pull-right" + .application-img + = image_tag app[:image], class: "img-responsive" + .application-authorizations + - if app[:authorizations].count > 0 + %h4="#{app[:name]} #{t("user_applications.index.access")}" + %ul + - app[:authorizations].each do |authorization| + %li + %b= t("user_applications.scopes.#{authorization}.name") + %p= t("user_applications.scopes.#{authorization}.description") + - else + .well + =t("user_applications.show.no_requirement") + = form_for "application", url: "#{api_openid_connect_authorizations_path}/#{app[:id]}", + html: { method: :delete, class: "form-horizontal"} do |f| + .clearfix= f.submit t("user_applications.revoke_autorization"), class: "btn btn-primary pull-right" - else .well From 3fb2d262b857b6764f48df4c21df36eae0efa467 Mon Sep 17 00:00:00 2001 From: augier Date: Sun, 2 Aug 2015 12:17:46 +0200 Subject: [PATCH 044/105] Using entypo icon as default application image --- app/assets/stylesheets/mobile/settings.scss | 30 +++++++++++-------- app/assets/stylesheets/user_applications.scss | 19 ++++++++++-- .../api/openid_connect/o_auth_application.rb | 4 --- app/presenters/user_applications_presenter.rb | 2 +- .../_add_remove_applications.haml | 5 +++- app/views/user_applications/index.mobile.haml | 3 +- 6 files changed, 41 insertions(+), 22 deletions(-) diff --git a/app/assets/stylesheets/mobile/settings.scss b/app/assets/stylesheets/mobile/settings.scss index a19241e60..c45814a7d 100644 --- a/app/assets/stylesheets/mobile/settings.scss +++ b/app/assets/stylesheets/mobile/settings.scss @@ -30,24 +30,30 @@ } } -#blocked_people { - .blocked_person { - border-bottom: 1px solid $border-grey; - margin-top: 0; - .avatar { max-width: 35px; } - .info { color: $text; } - } -} - .applications-page { + .applications-explenation { + margin-bottom: 15px; + } + .application-img { display: inline; + padding: 9px 0; float: left; + text-align: center; - img { + &, img { width: 70px; max-height: 70px; - margin: 9px 0; + } + + [class^="entypo-"] { + font-size: 60px; + height: 0; + width: 100%; + &::before { + position: relative; + top: -15px; + } } } @@ -57,4 +63,4 @@ display: inline-block; float: right; } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/user_applications.scss b/app/assets/stylesheets/user_applications.scss index 32cadd102..1290a5abd 100644 --- a/app/assets/stylesheets/user_applications.scss +++ b/app/assets/stylesheets/user_applications.scss @@ -1,12 +1,27 @@ .applications-page { + .applications-explenation { + margin-bottom: 15px; + } + .application-img { display: inline; + padding: 9px 0; float: left; + text-align: center; - img { + &, img { width: 70px; max-height: 70px; - margin: 9px 0; + } + + [class^="entypo-"] { + font-size: 60px; + height: 0; + width: 100%; + &::before { + position: relative; + top: -15px; + } } } diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 6589cdec4..5ecf8ea69 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -19,10 +19,6 @@ module Api self.client_secret = SecureRandom.hex(32) end - def image_uri - logo_uri ? logo_uri : "branding/logos/asterisk.png" - end - class << self def available_response_types ["id_token", "id_token token", "code"] diff --git a/app/presenters/user_applications_presenter.rb b/app/presenters/user_applications_presenter.rb index 5a0ad9cfc..5d400822d 100644 --- a/app/presenters/user_applications_presenter.rb +++ b/app/presenters/user_applications_presenter.rb @@ -23,7 +23,7 @@ class UserApplicationsPresenter { id: find_id(application), name: application.client_name, - image: application.image_uri, + image: application.logo_uri, authorizations: find_scopes(application) } end diff --git a/app/views/user_applications/_add_remove_applications.haml b/app/views/user_applications/_add_remove_applications.haml index 4f0c082d0..db71c35c9 100644 --- a/app/views/user_applications/_add_remove_applications.haml +++ b/app/views/user_applications/_add_remove_applications.haml @@ -3,7 +3,10 @@ - @user_apps.user_applications.each do |app| %li.list-group-item .application-img - = image_tag app[:image], class: "img-responsive" + - if app[:image] + = image_tag app[:image], class: "img-responsive" + - else + %i.entypo-browser .application-authorizations - if app[:authorizations].count > 0 %h4="#{app[:name]} #{t("user_applications.index.access")}" diff --git a/app/views/user_applications/index.mobile.haml b/app/views/user_applications/index.mobile.haml index 696ce47d8..27d0b6b82 100644 --- a/app/views/user_applications/index.mobile.haml +++ b/app/views/user_applications/index.mobile.haml @@ -6,9 +6,8 @@ .container-fluid .row - .col-md-12 + .col-md-12.applications-explenation = t('.applications_explanation') - .small-horizontal-spacer .col-md-12 = render 'add_remove_applications' From 07c12ba0579f05fe481b1c12310dcb0521840e25 Mon Sep 17 00:00:00 2001 From: augier Date: Sun, 2 Aug 2015 12:48:41 +0200 Subject: [PATCH 045/105] Using Camo for the application logo --- app/assets/stylesheets/mobile/settings.scss | 16 ++++++------ app/assets/stylesheets/user_applications.scss | 16 ++++++------ .../authorizations_controller.rb | 2 +- .../api/openid_connect/o_auth_application.rb | 4 +++ app/presenters/user_applications_presenter.rb | 2 +- .../_add_remove_applications.haml | 4 +-- features/desktop/user_applications.feature | 24 ++++++++++++++++++ features/mobile/user_applications.feature | 25 +++++++++++++++++++ .../step_definitions/oidc_common_steps.rb | 11 ++++++++ .../user_applications_steps.rb | 16 ++++++++++++ features/support/paths.rb | 2 ++ spec/factories.rb | 6 +++++ 12 files changed, 106 insertions(+), 22 deletions(-) create mode 100644 features/desktop/user_applications.feature create mode 100644 features/mobile/user_applications.feature create mode 100644 features/step_definitions/user_applications_steps.rb diff --git a/app/assets/stylesheets/mobile/settings.scss b/app/assets/stylesheets/mobile/settings.scss index c45814a7d..9b614aa66 100644 --- a/app/assets/stylesheets/mobile/settings.scss +++ b/app/assets/stylesheets/mobile/settings.scss @@ -36,19 +36,17 @@ } .application-img { - display: inline; - padding: 9px 0; + margin: 9px 0; float: left; + width: 60px; + max-height: 60px; text-align: center; - &, img { - width: 70px; - max-height: 70px; - } - [class^="entypo-"] { font-size: 60px; - height: 0; + height: 60px; + margin: 0; + padding: 0; width: 100%; &::before { position: relative; @@ -58,7 +56,7 @@ } .application-authorizations { - width: calc(100% - 70px); + width: calc(100% - 60px); padding: 0 0 15px 15px; display: inline-block; float: right; diff --git a/app/assets/stylesheets/user_applications.scss b/app/assets/stylesheets/user_applications.scss index 1290a5abd..c99ed3672 100644 --- a/app/assets/stylesheets/user_applications.scss +++ b/app/assets/stylesheets/user_applications.scss @@ -4,19 +4,17 @@ } .application-img { - display: inline; - padding: 9px 0; + margin: 9px 0; float: left; + width: 60px; + max-height: 60px; text-align: center; - &, img { - width: 70px; - max-height: 70px; - } - [class^="entypo-"] { font-size: 60px; - height: 0; + height: 60px; + margin: 0; + padding: 0; width: 100%; &::before { position: relative; @@ -26,7 +24,7 @@ } .application-authorizations { - width: calc(100% - 70px); + width: calc(100% - 60px); padding: 0 0 15px 15px; display: inline-block; float: right; diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 3a57a0832..706062744 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -22,7 +22,7 @@ module Api begin Api::OpenidConnect::Authorization.find_by(id: params[:id]).destroy rescue - # TODO: Log something here? + logger.error "Error while trying revoke inexistant authorization #{params[:id]}" end redirect_to user_applications_url end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 5ecf8ea69..58a03b3ca 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -19,6 +19,10 @@ module Api self.client_secret = SecureRandom.hex(32) end + def image_uri + logo_uri ? Diaspora::Camo.image_url(logo_uri) : nil + end + class << self def available_response_types ["id_token", "id_token token", "code"] diff --git a/app/presenters/user_applications_presenter.rb b/app/presenters/user_applications_presenter.rb index 5d400822d..5a0ad9cfc 100644 --- a/app/presenters/user_applications_presenter.rb +++ b/app/presenters/user_applications_presenter.rb @@ -23,7 +23,7 @@ class UserApplicationsPresenter { id: find_id(application), name: application.client_name, - image: application.logo_uri, + image: application.image_uri, authorizations: find_scopes(application) } end diff --git a/app/views/user_applications/_add_remove_applications.haml b/app/views/user_applications/_add_remove_applications.haml index db71c35c9..9ca522541 100644 --- a/app/views/user_applications/_add_remove_applications.haml +++ b/app/views/user_applications/_add_remove_applications.haml @@ -1,7 +1,7 @@ - if @user_apps.applications? %ul.list-group - @user_apps.user_applications.each do |app| - %li.list-group-item + %li.list-group-item.authorized-application .application-img - if app[:image] = image_tag app[:image], class: "img-responsive" @@ -20,7 +20,7 @@ =t("user_applications.show.no_requirement") = form_for "application", url: "#{api_openid_connect_authorizations_path}/#{app[:id]}", html: { method: :delete, class: "form-horizontal"} do |f| - .clearfix= f.submit t("user_applications.revoke_autorization"), class: "btn btn-primary pull-right" + .clearfix= f.submit t("user_applications.revoke_autorization"), class: "btn btn-danger pull-right app-revoke" - else .well diff --git a/features/desktop/user_applications.feature b/features/desktop/user_applications.feature new file mode 100644 index 000000000..99a2f8843 --- /dev/null +++ b/features/desktop/user_applications.feature @@ -0,0 +1,24 @@ +@javascript +Feature: managing authorized applications + Background: + Given following users exist: + | username | email | + | Augier | augier@example.org | + And all scopes exist + And a client with a provided picture exists for user "augier@example.org" + And a client exists for user "augier@example.org" + + Scenario: displaying authorizations + When I sign in as "augier@example.org" + And I go to the user applications page + Then I should see 2 authorized applications + And I should see 1 authorized applications with no provided image + And I should see 1 authorized applications with an image + + Scenario: revoke an authorization + When I sign in as "augier@example.org" + And I go to the user applications page + And I revoke the first authorization + Then I should see 1 authorized applications + And I revoke the first authorization + Then I should see 0 authorized applications diff --git a/features/mobile/user_applications.feature b/features/mobile/user_applications.feature new file mode 100644 index 000000000..18895e3ce --- /dev/null +++ b/features/mobile/user_applications.feature @@ -0,0 +1,25 @@ +@mobile +@javascript +Feature: managing authorized applications + Background: + Given following users exist: + | username | email | + | Augier | augier@example.org | + And all scopes exist + And a client with a provided picture exists for user "augier@example.org" + And a client exists for user "augier@example.org" + + Scenario: displaying authorizations + When I sign in as "augier@example.org" + And I go to the user applications page + Then I should see 2 authorized applications + And I should see 1 authorized applications with no provided image + And I should see 1 authorized applications with an image + + Scenario: revoke an authorization + When I sign in as "augier@example.org" + And I go to the user applications page + And I revoke the first authorization + Then I should see 1 authorized applications + And I revoke the first authorization + Then I should see 0 authorized applications diff --git a/features/step_definitions/oidc_common_steps.rb b/features/step_definitions/oidc_common_steps.rb index bcf8be96e..e9a812d41 100644 --- a/features/step_definitions/oidc_common_steps.rb +++ b/features/step_definitions/oidc_common_steps.rb @@ -3,6 +3,17 @@ Given(/^all scopes exist$/) do Api::OpenidConnect::Scope.find_or_create_by(name: "read") end +Given /^a client with a provided picture exists for user "([^\"]*)"$/ do |email| + app = FactoryGirl.create(:o_auth_application_with_image) + user = User.find_by(email: email) + FactoryGirl.create(:auth_with_read, user: user, o_auth_application: app) +end + +Given /^a client exists for user "([^\"]*)"$/ do |email| + user = User.find_by(email: email) + FactoryGirl.create(:auth_with_read, user: user) +end + When /^I register a new client$/ do post api_openid_connect_clients_path, redirect_uris: ["http://localhost:3000"], client_name: "diaspora client" end diff --git a/features/step_definitions/user_applications_steps.rb b/features/step_definitions/user_applications_steps.rb new file mode 100644 index 000000000..7cef79050 --- /dev/null +++ b/features/step_definitions/user_applications_steps.rb @@ -0,0 +1,16 @@ +Then /^I should see (\d+) authorized applications$/ do |num| + expect(page).to have_selector(".applications-page", count: 1) + expect(page).to have_selector(".authorized-application", count: num.to_i) +end + +Then /^I should see (\d+) authorized applications with no provided image$/ do |num| + expect(page).to have_selector(".application-img > .entypo-browser", count: num.to_i) +end + +Then /^I should see (\d+) authorized applications with an image$/ do |num| + expect(page).to have_selector(".application-img > .img-responsive", count: num.to_i) +end + +When /^I revoke the first authorization$/ do + find(".app-revoke", match: :first).click +end diff --git a/features/support/paths.rb b/features/support/paths.rb index 1d512dc43..19ada8e40 100644 --- a/features/support/paths.rb +++ b/features/support/paths.rb @@ -36,6 +36,8 @@ module NavigationHelpers edit_user_path when /^forgot password page$/ new_user_password_path + when /^user applications page$/ + user_applications_path when %r{^"(/.*)"} Regexp.last_match(1) else diff --git a/spec/factories.rb b/spec/factories.rb index 946985a95..ce54a4aa4 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -315,6 +315,12 @@ FactoryGirl.define do redirect_uris ["http://localhost:3000/"] end + factory :o_auth_application_with_image, class: Api::OpenidConnect::OAuthApplication do + client_name "Diaspora Test Client" + redirect_uris ["http://localhost:3000/"] + logo_uri "/assets/user/default.png" + end + factory :o_auth_application_with_ppid, class: Api::OpenidConnect::OAuthApplication do client_name "Diaspora Test Client" redirect_uris ["http://localhost:3000/"] From 4dae744a4a047031f13cfcac8513ddec1a190d2e Mon Sep 17 00:00:00 2001 From: theworldbright Date: Tue, 4 Aug 2015 20:09:37 +0900 Subject: [PATCH 046/105] Adjust translations for user applications page --- config/locales/diaspora/en.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 83b4ebc34..3bebdaf2c 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -1481,22 +1481,22 @@ en: index: edit_applications: "Applications" title: "Your authorized applications" - access: "is authorized to access to:" - no_requirement: "This application requires no autorizations" - applications_explanation: "Here are listed the applications to which you autorized the access to your profile informations" - no_applications: "You have no authorized application for now" - revoke_autorization: "Revoke autorization" + access: "is authorized access to:" + no_requirement: "This application requires no permissions" + applications_explanation: "Here is a list of applications that you to which have authorized your profile information" + no_applications: "You have no authorized applications for now" + revoke_autorization: "Revoke" scopes: openid: name: "basic profile" - description: "This allows the application to read informations of your basic profile" + description: "This allows the application to read your basic profile" extended: name: "extended profile" - description: "This allows the application to read informations of your extended profile" + description: "This allows the application to read your extended profile" read: name: "read profile, stream and conversations" description: "This allows the application to read your stream, your conversations and your complete profile" write: - name: "send post, conversations and likes" - description: "This allows the application to send new posts, start conversations or send a new messages in an existant conversation and send likes" + name: "send posts, conversations and reactions" + description: "This allows the application to send new posts, write conversations, and send reactions" From 8be3be3e1038c9bfb4df5d8e73b47055cef36b2c Mon Sep 17 00:00:00 2001 From: theworldbright Date: Thu, 6 Aug 2015 15:59:09 +0900 Subject: [PATCH 047/105] Refactor authorizations controller destroy action --- .../authorizations_controller.rb | 10 +++++----- .../authorizations_controller_spec.rb | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 706062744..25bc0d957 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -18,11 +18,11 @@ module Api end def destroy - # TODO: Specs - begin - Api::OpenidConnect::Authorization.find_by(id: params[:id]).destroy - rescue - logger.error "Error while trying revoke inexistant authorization #{params[:id]}" + authorization = Api::OpenidConnect::Authorization.find_by(id: params[:id]) + if authorization + authorization.destroy + else + raise ArgumentError, "Error while trying revoke non-existent authorization with ID #{params[:id]}" end redirect_to user_applications_url end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index d60ec9c1e..fdc69afad 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -3,6 +3,7 @@ require "spec_helper" describe Api::OpenidConnect::AuthorizationsController, type: :controller do let!(:client) { FactoryGirl.create(:o_auth_application) } let!(:client_with_multiple_redirects) { FactoryGirl.create(:o_auth_application_with_multiple_redirects) } + let!(:auth_with_read) { FactoryGirl.create(:auth_with_read) } before do sign_in :user, alice @@ -218,4 +219,22 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do end end end + + describe "#destroy" do + context "with existent authorization" do + before do + delete :destroy, id: auth_with_read.id + end + + it "removes the authorization" do + expect(Api::OpenidConnect::Authorization.find_by(id: auth_with_read.id)).to be_nil + end + end + + context "with non-existent authorization" do + it "raises an error" do + expect{ delete :destroy, id: 123456789 }.to raise_error(ArgumentError) + end + end + end end From 25f51c606a7b5762919e2b37fb12c786477da1da Mon Sep 17 00:00:00 2001 From: theworldbright Date: Thu, 6 Aug 2015 19:41:40 +0900 Subject: [PATCH 048/105] Add support for prompt parameter --- .../authorizations_controller.rb | 74 +++++++++++++++++-- app/presenters/user_applications_presenter.rb | 2 +- .../step_definitions/implicit_flow_steps.rb | 1 + .../authorizations_controller_spec.rb | 68 +++++++++++++++++ 4 files changed, 139 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 25bc0d957..148441fb9 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -9,7 +9,13 @@ module Api before_action :authenticate_user! def new - request_authorization_consent_form + auth = Api::OpenidConnect::Authorization.find_by_client_id_and_user(params[:client_id], current_user) + if params[:prompt] + prompt = params[:prompt].split(" ") + handle_prompt(prompt, auth) + else + handle_authorization_form(auth) + end end def create @@ -29,12 +35,50 @@ module Api private - def request_authorization_consent_form # TODO: Add support for prompt params - if Api::OpenidConnect::Authorization.find_by_client_id_and_user(params[:client_id], current_user) + def handle_prompt(prompt, auth) + if prompt.include? "select_account" + handle_prompt_params_error("account_selection_required", + "There is no support for choosing among multiple accounts") + elsif prompt.include? "none" + handle_prompt_none(prompt, auth) + elsif prompt.include?("login") && logged_in_more_than_5_minutes_ago? + handle_prompt_params_error("login_required", + "There is no support for re-authenticating already authenticated users") + elsif prompt.include? "consent" + request_authorization_consent_form + else + handle_authorization_form(auth) + end + end + + def handle_authorization_form(auth) + if auth process_authorization_consent("true") else - endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user) - handle_start_point_response(endpoint) + request_authorization_consent_form + end + end + + def request_authorization_consent_form + endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user) + handle_start_point_response(endpoint) + end + + def logged_in_more_than_5_minutes_ago? + (current_user.current_sign_in_at.to_i - Time.zone.now.to_i) > 300 + end + + def handle_prompt_none(prompt, auth) + if prompt == ["none"] + if auth + process_authorization_consent("true") + else + handle_prompt_params_error("interaction_required", + "The Authentication Request cannot be completed without end-user interaction") + end + else + handle_prompt_params_error("invalid_request", + "The 'none' value cannot be used with any other prompt value") end end @@ -115,6 +159,26 @@ module Api session[:response_type] end end + + def handle_prompt_params_error(error, error_description) + if params[:client_id] && params[:redirect_uri] + app = Api::OpenidConnect::OAuthApplication.find_by(client_id: params[:client_id]) + if app && app.redirect_uris.include?(params[:redirect_uri]) + redirect_prompt_error_display(error, error_description) + else + render json: {error: "bad_request", + description: "No client with client_id " + params[:client_id] + " found"} + end + else + render json: {error: "bad_request", description: "Missing client id or redirect URI"} + end + end + + def redirect_prompt_error_display(error, error_description) + redirect_params_hash = {error: error, error_description: error_description, state: params[:state]} + redirect_fragment = redirect_params_hash.compact.map {|key, value| key.to_s + "=" + value }.join("&") + redirect_to params[:redirect_uri] + "#" + redirect_fragment + end end end end diff --git a/app/presenters/user_applications_presenter.rb b/app/presenters/user_applications_presenter.rb index 5a0ad9cfc..8c2d263fd 100644 --- a/app/presenters/user_applications_presenter.rb +++ b/app/presenters/user_applications_presenter.rb @@ -31,7 +31,7 @@ class UserApplicationsPresenter def find_scopes(application) scopes = Api::OpenidConnect::Authorization.find_by_client_id_and_user( application.client_id, @current_user).scopes - scopes.each_with_object([]){|scope, array| array << scope.name } + scopes.each_with_object([]) {|scope, array| array << scope.name } end def find_id(application) diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb index 4516ab55d..844cf3f96 100644 --- a/features/step_definitions/implicit_flow_steps.rb +++ b/features/step_definitions/implicit_flow_steps.rb @@ -4,6 +4,7 @@ o_auth_query_params = %i( scope=openid%20read nonce=hello state=hi + prompt=login ).join("&") Given /^I send a post request from that client to the implicit flow authorization endpoint$/ do diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index fdc69afad..4e616388a 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -84,6 +84,51 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do expect(response.location).to match("error=invalid_request") end end + + context "when prompt is none" do + it "should return an interaction required error" do + post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" + expect(response.location).to match("error=interaction_required") + expect(response.location).to match("state=1234") + end + end + + context "when prompt is none and consent" do + it "should return an interaction required error" do + post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none consent" + expect(response.location).to match("error=invalid_request") + expect(response.location).to match("state=1234") + end + end + + context "when prompt is select_account" do + it "should return an account_selection_required error" do + post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "select_account" + expect(response.location).to match("error=account_selection_required") + expect(response.location).to match("state=1234") + end + end + + context "when prompt is none and client ID is invalid" do + it "should return an account_selection_required error" do + post :new, client_id: "random", redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" + json_body = JSON.parse(response.body) + expect(json_body["error"]).to match("bad_request") + end + end + + context "when prompt is none and redirect URI does not match pre-registered URIs" do + it "should return an account_selection_required error" do + post :new, client_id: client.client_id, redirect_uri: "http://randomuri:3000/", + response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" + json_body = JSON.parse(response.body) + expect(json_body["error"]).to match("bad_request") + end + end end context "when already authorized" do let!(:auth) { @@ -110,6 +155,29 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do expect(response.location).to have_content("state=4130930983") end end + + context "when prompt is none" do + it "should return the id token in a fragment" do + post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", nonce: 413_093_098_3, state: 413_093_098_3, + display: "page", prompt: "none" + expect(response.location).to have_content("id_token=") + encoded_id_token = response.location[/(?<=id_token=)[^&]+/] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, + Api::OpenidConnect::IdTokenConfig.public_key + expect(decoded_token.nonce).to eq("4130930983") + expect(decoded_token.exp).to be > Time.zone.now.utc.to_i + end + end + + context "when prompt contains consent" do + it "should return a consent form page" do + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", nonce: 413_093_098_3, state: 413_093_098_3, + display: "page", prompt: "consent" + expect(response.body).to match("Diaspora Test Client") + end + end end end From ab65617958aa12eacc369f547ef4802e2660d1b9 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 7 Aug 2015 02:29:11 +0900 Subject: [PATCH 049/105] Add support for max_age parameter Additionally add support for prompt's login option Signed-off-by: theworldbright --- .../authorizations_controller.rb | 24 ++++++++++++++----- features/desktop/oidc_implicit_flow.feature | 16 ++++++++++--- .../step_definitions/implicit_flow_steps.rb | 24 +++++++++++++++++-- .../step_definitions/password_flow_steps.rb | 0 .../authorizations_controller_spec.rb | 2 +- 5 files changed, 54 insertions(+), 12 deletions(-) delete mode 100644 features/step_definitions/password_flow_steps.rb diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 148441fb9..e31d001bd 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -10,7 +10,9 @@ module Api def new auth = Api::OpenidConnect::Authorization.find_by_client_id_and_user(params[:client_id], current_user) - if params[:prompt] + if logged_in_before?(params[:max_age]) + reauthenticate + elsif params[:prompt] prompt = params[:prompt].split(" ") handle_prompt(prompt, auth) else @@ -41,9 +43,8 @@ module Api "There is no support for choosing among multiple accounts") elsif prompt.include? "none" handle_prompt_none(prompt, auth) - elsif prompt.include?("login") && logged_in_more_than_5_minutes_ago? - handle_prompt_params_error("login_required", - "There is no support for re-authenticating already authenticated users") + elsif prompt.include?("login") && logged_in_before?(60) + reauthenticate elsif prompt.include? "consent" request_authorization_consent_form else @@ -51,6 +52,13 @@ module Api end end + def reauthenticate + sign_out current_user + params_as_get_query = params.map {|key, value| key.to_s + "=" + value }.join("&") + authorization_path_with_query = new_api_openid_connect_authorization_path + "?" + params_as_get_query + redirect_to authorization_path_with_query + end + def handle_authorization_form(auth) if auth process_authorization_consent("true") @@ -64,8 +72,12 @@ module Api handle_start_point_response(endpoint) end - def logged_in_more_than_5_minutes_ago? - (current_user.current_sign_in_at.to_i - Time.zone.now.to_i) > 300 + def logged_in_before?(seconds) + if seconds.nil? + false + else + (Time.zone.now.utc.to_i - current_user.current_sign_in_at.to_i) > seconds.to_i + end end def handle_prompt_none(prompt, auth) diff --git a/features/desktop/oidc_implicit_flow.feature b/features/desktop/oidc_implicit_flow.feature index 480b0a928..8ded34fd3 100644 --- a/features/desktop/oidc_implicit_flow.feature +++ b/features/desktop/oidc_implicit_flow.feature @@ -6,20 +6,30 @@ Feature: Access protected resources using implicit flow Scenario: Invalid client id to auth endpoint When I register a new client - And I send a post request from that client to the implicit flow authorization endpoint using a invalid client id + And I send a post request from that client to the authorization endpoint using a invalid client id And I sign in as "kent@kent.kent" Then I should see an "bad_request" error Scenario: Application is denied authorization When I register a new client - And I send a post request from that client to the implicit flow authorization endpoint + And I send a post request from that client to the authorization endpoint And I sign in as "kent@kent.kent" And I deny authorization to the client Then I should not see any tokens in the redirect url Scenario: Application is authorized When I register a new client - And I send a post request from that client to the implicit flow authorization endpoint + And I send a post request from that client to the authorization endpoint + And I sign in as "kent@kent.kent" + And I give my consent and authorize the client + And I parse the bearer tokens and use it to access user info + Then I should receive "kent"'s id, username, and email + + Scenario: Application is authorized and uses small value for the max_age parameter + When I register a new client + And I sign in as "kent@kent.kent" + And I pass time + And I send a post request from that client to the authorization endpoint with max age And I sign in as "kent@kent.kent" And I give my consent and authorize the client And I parse the bearer tokens and use it to access user info diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb index 844cf3f96..5e07969bf 100644 --- a/features/step_definitions/implicit_flow_steps.rb +++ b/features/step_definitions/implicit_flow_steps.rb @@ -7,13 +7,33 @@ o_auth_query_params = %i( prompt=login ).join("&") -Given /^I send a post request from that client to the implicit flow authorization endpoint$/ do +o_auth_query_params_with_max_age = %i( + redirect_uri=http://localhost:3000 + response_type=id_token%20token + scope=openid%20read + nonce=hello + state=hi + prompt=login + max_age=30 +).join("&") + +Given /^I send a post request from that client to the authorization endpoint$/ do client_json = JSON.parse(last_response.body) visit new_api_openid_connect_authorization_path + "?client_id=#{client_json['client_id']}&#{o_auth_query_params}" end -Given /^I send a post request from that client to the implicit flow authorization endpoint using a invalid client id/ do +Given /^I pass time$/ do + Timecop.travel(Time.zone.now + 1.minute) +end + +Given /^I send a post request from that client to the authorization endpoint with max age$/ do + client_json = JSON.parse(last_response.body) + visit new_api_openid_connect_authorization_path + + "?client_id=#{client_json['client_id']}&#{o_auth_query_params_with_max_age}" +end + +Given /^I send a post request from that client to the authorization endpoint using a invalid client id$/ do visit new_api_openid_connect_authorization_path + "?client_id=randomid&#{o_auth_query_params}" end diff --git a/features/step_definitions/password_flow_steps.rb b/features/step_definitions/password_flow_steps.rb deleted file mode 100644 index e69de29bb..000000000 diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 4e616388a..244799a28 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -301,7 +301,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do context "with non-existent authorization" do it "raises an error" do - expect{ delete :destroy, id: 123456789 }.to raise_error(ArgumentError) + expect { delete :destroy, id: 123_456_789 }.to raise_error(ArgumentError) end end end From 24fd70676c55b55cadf8fd345ab00269d3b7f852 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 7 Aug 2015 15:46:32 +0900 Subject: [PATCH 050/105] Fix webfinger discovery route --- app/controllers/api/openid_connect/discovery_controller.rb | 2 +- config/routes.rb | 4 ++-- .../api/openid_connect/discovery_controller_spec.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index 7c92ecbd7..df1cfec34 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -5,7 +5,7 @@ module Api jrd = { links: [{ rel: OpenIDConnect::Discovery::Provider::Issuer::REL_VALUE, - href: File.join(root_url, "api", "openid_connect") + href: root_url }] } jrd[:subject] = params[:resource] if params[:resource].present? diff --git a/config/routes.rb b/config/routes.rb index b38cd165c..a92555038 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -247,13 +247,13 @@ Diaspora::Application.routes.draw do resources :authorizations, only: %i(new create destroy) post "authorizations/new", to: "authorizations#new" - get ".well-known/webfinger", to: "discovery#webfinger" - get ".well-known/openid-configuration", to: "discovery#configuration" get "jwks.json", to: "id_tokens#jwks" get "user_info", to: "user_info#show" end end + get ".well-known/webfinger", to: "api/openid_connect/discovery#webfinger" + get ".well-known/openid-configuration", to: "api/openid_connect/discovery#configuration" get "user_applications", to: "user_applications#index" end diff --git a/spec/controllers/api/openid_connect/discovery_controller_spec.rb b/spec/controllers/api/openid_connect/discovery_controller_spec.rb index 0233ae180..77de08ea0 100644 --- a/spec/controllers/api/openid_connect/discovery_controller_spec.rb +++ b/spec/controllers/api/openid_connect/discovery_controller_spec.rb @@ -8,7 +8,7 @@ describe Api::OpenidConnect::DiscoveryController, type: :controller do it "should return a url to the openid-configuration" do json_body = JSON.parse(response.body) - expect(json_body["links"].first["href"]).to eq("http://test.host/api/openid_connect") + expect(json_body["links"].first["href"]).to eq("http://test.host/") end it "should return the resource in the subject" do From bb8fe6aa83dd89114beb8aa2dd948edee8fa5db4 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 7 Aug 2015 18:38:48 +0900 Subject: [PATCH 051/105] Adjust id token config to save private key to file --- .gitignore | 1 + .../api/openid_connect/id_tokens_controller.rb | 2 +- app/models/api/openid_connect/id_token.rb | 2 +- features/step_definitions/auth_code_steps.rb | 2 +- lib/api/openid_connect/id_token_config.rb | 17 +++++++++++------ .../authorizations_controller_spec.rb | 10 +++++----- .../openid_connect/id_tokens_controller_spec.rb | 2 +- .../api/openid_connect/token_endpoint_spec.rb | 4 ++-- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 70544e847..3847784e1 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ vendor/cache/ config/database.yml .rvmrc_custom .rvmrc.local +oidc_key.pem # Mailing list stuff config/email_offset diff --git a/app/controllers/api/openid_connect/id_tokens_controller.rb b/app/controllers/api/openid_connect/id_tokens_controller.rb index dae760892..9e8c65b89 100644 --- a/app/controllers/api/openid_connect/id_tokens_controller.rb +++ b/app/controllers/api/openid_connect/id_tokens_controller.rb @@ -8,7 +8,7 @@ module Api private def build_jwk - JSON::JWK.new(Api::OpenidConnect::IdTokenConfig.public_key, use: :sig) + JSON::JWK.new(Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY, use: :sig) end end end diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb index 2855a38b0..5acfa0ea4 100644 --- a/app/models/api/openid_connect/id_token.rb +++ b/app/models/api/openid_connect/id_token.rb @@ -12,7 +12,7 @@ module Api end def to_jwt(options={}) - to_response_object(options).to_jwt OpenidConnect::IdTokenConfig.private_key + to_response_object(options).to_jwt OpenidConnect::IdTokenConfig::PRIVATE_KEY end def to_response_object(options={}) diff --git a/features/step_definitions/auth_code_steps.rb b/features/step_definitions/auth_code_steps.rb index 36ea99ced..37b4f7b8b 100644 --- a/features/step_definitions/auth_code_steps.rb +++ b/features/step_definitions/auth_code_steps.rb @@ -30,7 +30,7 @@ When /^I parse the tokens and use it obtain user info$/ do access_token = client_json["access_token"] encoded_id_token = client_json["id_token"] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - Api::OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY expect(decoded_token.sub).to eq(@me.diaspora_handle) get api_openid_connect_user_info_path, access_token: access_token end diff --git a/lib/api/openid_connect/id_token_config.rb b/lib/api/openid_connect/id_token_config.rb index d046d4b7a..541fa4663 100644 --- a/lib/api/openid_connect/id_token_config.rb +++ b/lib/api/openid_connect/id_token_config.rb @@ -1,13 +1,18 @@ module Api module OpenidConnect class IdTokenConfig - @@key = OpenSSL::PKey::RSA.new(2048) - def self.public_key - @@key.public_key - end - def self.private_key - @@key + private_key = OpenSSL::PKey::RSA.new(2048) + key_file_path = File.join(Rails.root, "config", "oidc_key.pem") + if File.exist?(key_file_path) + private_key = OpenSSL::PKey::RSA.new(File.read(key_file_path)) + else + open key_file_path, "w" do |io| + io.write private_key.to_pem + end + File.chmod(0600, key_file_path) end + PRIVATE_KEY = private_key + PUBLIC_KEY = private_key.public_key end end end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 244799a28..5233ef976 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -146,7 +146,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do expect(response.location).to have_content("id_token=") encoded_id_token = response.location[/(?<=id_token=)[^&]+/] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - Api::OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY expect(decoded_token.nonce).to eq("4130930983") expect(decoded_token.exp).to be > Time.zone.now.utc.to_i end @@ -164,7 +164,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do expect(response.location).to have_content("id_token=") encoded_id_token = response.location[/(?<=id_token=)[^&]+/] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - Api::OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY expect(decoded_token.nonce).to eq("4130930983") expect(decoded_token.exp).to be > Time.zone.now.utc.to_i end @@ -196,7 +196,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return the id token in a fragment" do encoded_id_token = response.location[/(?<=id_token=)[^&]+/] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - Api::OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY expect(decoded_token.nonce).to eq("4180930983") expect(decoded_token.exp).to be > Time.zone.now.utc.to_i end @@ -204,7 +204,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return a valid access token in a fragment" do encoded_id_token = response.location[/(?<=id_token=)[^&]+/] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - Api::OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY access_token = response.location[/(?<=access_token=)[^&]+/] access_token_check_num = UrlSafeBase64.encode64(OpenSSL::Digest::SHA256.digest(access_token)[0, 128 / 8]) expect(decoded_token.at_hash).to eq(access_token_check_num) @@ -227,7 +227,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do expect(response.location).to have_content("id_token=") encoded_id_token = response.location[/(?<=id_token=)[^&]+/] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - Api::OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY expect(decoded_token.nonce).to eq("4180930983") expect(decoded_token.exp).to be > Time.zone.now.utc.to_i end diff --git a/spec/controllers/api/openid_connect/id_tokens_controller_spec.rb b/spec/controllers/api/openid_connect/id_tokens_controller_spec.rb index 06930c45b..6827863d5 100644 --- a/spec/controllers/api/openid_connect/id_tokens_controller_spec.rb +++ b/spec/controllers/api/openid_connect/id_tokens_controller_spec.rb @@ -13,7 +13,7 @@ describe Api::OpenidConnect::IdTokensController, type: :controller do JSON::JWK.decode jwk end public_key = public_keys.first - expect(Api::OpenidConnect::IdTokenConfig.private_key.public_key.to_s).to eq(public_key.to_s) + expect(Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY.to_s).to eq(public_key.to_s) end end end diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index 1dad2c133..dfbee1eeb 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -21,7 +21,7 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do json = JSON.parse(response.body) encoded_id_token = json["id_token"] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - Api::OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY expected_guid = bob.pairwise_pseudonymous_identifiers.find_by(sector_identifier: "https://example.com/uri").guid expect(decoded_token.sub).to eq(expected_guid) expect(decoded_token.exp).to be > Time.zone.now.utc.to_i @@ -31,7 +31,7 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do json = JSON.parse(response.body) encoded_id_token = json["id_token"] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, - Api::OpenidConnect::IdTokenConfig.public_key + Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY access_token = json["access_token"] access_token_check_num = UrlSafeBase64.encode64(OpenSSL::Digest::SHA256.digest(access_token)[0, 128 / 8]) expect(decoded_token.at_hash).to eq(access_token_check_num) From 724f32604b417e89d3bd920808d6e04de3782645 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 7 Aug 2015 21:32:48 +0900 Subject: [PATCH 052/105] Add nonce to auth code flow --- app/models/api/openid_connect/authorization.rb | 2 +- db/migrate/20150708153926_create_authorizations.rb | 1 + db/schema.rb | 1 + .../authorization_point/endpoint_confirmation_point.rb | 7 ++++--- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index 639bf320a..fce5220fe 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -38,7 +38,7 @@ module Api # TODO: Add support for request object end - def create_id_token(nonce=nil) + def create_id_token id_tokens.create!(nonce: nonce) end diff --git a/db/migrate/20150708153926_create_authorizations.rb b/db/migrate/20150708153926_create_authorizations.rb index c99fa0e85..af659bcf0 100644 --- a/db/migrate/20150708153926_create_authorizations.rb +++ b/db/migrate/20150708153926_create_authorizations.rb @@ -6,6 +6,7 @@ class CreateAuthorizations < ActiveRecord::Migration t.string :refresh_token t.string :code t.string :redirect_uri + t.string :nonce t.timestamps null: false end diff --git a/db/schema.rb b/db/schema.rb index 09519a9e4..fa60983f8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -69,6 +69,7 @@ ActiveRecord::Schema.define(version: 20150801074555) do t.string "refresh_token", limit: 255 t.string "code", limit: 255 t.string "redirect_uri", limit: 255 + t.string "nonce", limit: 255 t.datetime "created_at", null: false t.datetime "updated_at", null: false end diff --git a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb index e15053e0e..62fa207a5 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -23,6 +23,7 @@ module Api def approved!(req, res) auth = OpenidConnect::Authorization.find_or_create_by( o_auth_application: @o_auth_application, user: @user, redirect_uri: @redirect_uri) + auth.nonce = req.nonce auth.scopes << @scopes handle_approved_response_type(auth, req, res) res.approve! @@ -32,7 +33,7 @@ module Api response_types = Array(req.response_type) handle_approved_auth_code(auth, res, response_types) handle_approved_access_token(auth, res, response_types) - handle_approved_id_token(auth, req, res, response_types) + handle_approved_id_token(auth, res, response_types) end def handle_approved_auth_code(auth, res, response_types) @@ -45,9 +46,9 @@ module Api res.access_token = auth.create_access_token end - def handle_approved_id_token(auth, req, res, response_types) + def handle_approved_id_token(auth, res, response_types) return unless response_types.include?(:id_token) - id_token = auth.create_id_token(req.nonce) + id_token = auth.create_id_token auth_code_value = res.respond_to?(:code) ? res.code : nil access_token_value = res.respond_to?(:access_token) ? res.access_token : nil res.id_token = id_token.to_jwt(code: auth_code_value, access_token: access_token_value) From 054e421829026d186d1bdbae4f404582b5595c70 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 7 Aug 2015 22:09:28 +0900 Subject: [PATCH 053/105] Remove zone info claim --- app/controllers/api/openid_connect/discovery_controller.rb | 2 +- app/serializers/user_info_serializer.rb | 6 +----- features/step_definitions/oidc_common_steps.rb | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index df1cfec34..c6c8f6cc1 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -26,7 +26,7 @@ module Api subject_types_supported: %w(public pairwise), id_token_signing_alg_values_supported: %i(RS256), token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), - claims_supported: %w(sub nickname profile picture zoneinfo) + claims_supported: %w(sub nickname profile picture) ) end end diff --git a/app/serializers/user_info_serializer.rb b/app/serializers/user_info_serializer.rb index 67cf3db11..86c89ef47 100644 --- a/app/serializers/user_info_serializer.rb +++ b/app/serializers/user_info_serializer.rb @@ -1,5 +1,5 @@ class UserInfoSerializer < ActiveModel::Serializer - attributes :sub, :nickname, :profile, :picture, :zoneinfo + attributes :sub, :nickname, :profile, :picture def sub auth = serialization_options[:authorization] @@ -24,8 +24,4 @@ class UserInfoSerializer < ActiveModel::Serializer def picture File.join(AppConfig.environment.url, object.image_url).to_s end - - def zoneinfo - object.language - end end diff --git a/features/step_definitions/oidc_common_steps.rb b/features/step_definitions/oidc_common_steps.rb index e9a812d41..f73d4407b 100644 --- a/features/step_definitions/oidc_common_steps.rb +++ b/features/step_definitions/oidc_common_steps.rb @@ -32,7 +32,6 @@ Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username| user = User.find_by_username(username) user_profile_url = File.join(AppConfig.environment.url, "people", user.guid).to_s expect(user_info_json["profile"]).to have_content(user_profile_url) - expect(user_info_json["zoneinfo"]).to have_content(user.language) end Then /^I should receive an "([^\"]*)" error$/ do |error_message| From 858e8c25030b887c988e6f374604ae9d2f7d0070 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 7 Aug 2015 22:58:03 +0900 Subject: [PATCH 054/105] Prevent duplicate scopes in authorization --- app/models/api/openid_connect/o_auth_application.rb | 2 +- .../authorization_point/endpoint_confirmation_point.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 58a03b3ca..a136fc123 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -1,7 +1,7 @@ module Api module OpenidConnect class OAuthApplication < ActiveRecord::Base - has_many :authorizations + has_many :authorizations, dependent: :destroy has_many :user, through: :authorizations validates :client_id, presence: true, uniqueness: true diff --git a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb index 62fa207a5..104b0a0fb 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -24,7 +24,7 @@ module Api auth = OpenidConnect::Authorization.find_or_create_by( o_auth_application: @o_auth_application, user: @user, redirect_uri: @redirect_uri) auth.nonce = req.nonce - auth.scopes << @scopes + auth.scopes << @scopes unless auth.scopes == @scopes handle_approved_response_type(auth, req, res) res.approve! end From 28fc65ae26f4faf59041589fdf5ed5907d6a00b9 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 9 Aug 2015 20:48:35 +0900 Subject: [PATCH 055/105] Add CORS support to OIDC --- app/controllers/api/v0/base_controller.rb | 2 ++ config/initializers/cors.rb | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/v0/base_controller.rb b/app/controllers/api/v0/base_controller.rb index edcd178ed..39d331215 100644 --- a/app/controllers/api/v0/base_controller.rb +++ b/app/controllers/api/v0/base_controller.rb @@ -3,6 +3,8 @@ module Api class BaseController < ApplicationController include Api::OpenidConnect::ProtectedResourceEndpoint + protected + def current_user current_token ? current_token.authorization.user : nil end diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 68e071e7c..e2afeff3e 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -1,7 +1,11 @@ Rails.application.config.middleware.insert 0, Rack::Cors do allow do - origins '*' - resource '/.well-known/host-meta' - resource '/webfinger' + origins "*" + resource "/.well-known/host-meta" + resource "/webfinger" + resource "/.well-known/webfinger" + resource "/.well-known/openid-configuration" + resource "/api/openid_connect/user_info", methods: :get + resource "/api/v0/*", methods: %i(get post delete) end end From e55a0b0d0be72c5562275d54013e89b0f9573f68 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 12 Aug 2015 20:48:19 +0900 Subject: [PATCH 056/105] Replace scopes with constants in Authorization --- .../authorizations_controller.rb | 2 +- .../openid_connect/discovery_controller.rb | 4 +-- .../openid_connect/user_info_controller.rb | 2 +- .../api/openid_connect/authorization.rb | 34 ++++++++++++------- app/presenters/user_applications_presenter.rb | 4 +-- .../authorizations/new.html.haml | 2 +- .../20150708153926_create_authorizations.rb | 1 + db/migrate/20150708154727_create_scope.rb | 9 ----- ..._create_authorization_scopes_join_table.rb | 8 ----- db/schema.rb | 15 +------- db/seeds.rb | 3 -- features/desktop/oidc_auth_code_flow.feature | 1 - features/desktop/oidc_implicit_flow.feature | 1 - .../step_definitions/oidc_common_steps.rb | 5 --- .../authorization_point/endpoint.rb | 7 ++-- .../endpoint_confirmation_point.rb | 17 +++++++--- .../protected_resource_endpoint.rb | 2 +- lib/api/openid_connect/token_endpoint.rb | 12 +------ .../authorizations_controller_spec.rb | 2 +- spec/factories.rb | 19 ++--------- .../api/openid_connect/token_endpoint_spec.rb | 10 +++--- spec/spec_helper.rb | 1 - 22 files changed, 57 insertions(+), 104 deletions(-) delete mode 100644 db/migrate/20150708154727_create_scope.rb delete mode 100644 db/migrate/20150708155202_create_authorization_scopes_join_table.rb delete mode 100644 db/seeds.rb diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index e31d001bd..5e6cc407c 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -122,7 +122,7 @@ module Api end def scopes_as_space_seperated_values - @scopes.map(&:name).join(" ") + @scopes.join(" ") end def process_authorization_consent(approvedString) diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index c6c8f6cc1..ca8dd18cf 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -19,8 +19,8 @@ module Api authorization_endpoint: new_api_openid_connect_authorization_url, token_endpoint: api_openid_connect_access_tokens_url, userinfo_endpoint: api_openid_connect_user_info_url, - jwks_uri: File.join(root_url, "api", "openid_connect", "jwks.json"), - scopes_supported: Api::OpenidConnect::Scope.pluck(:name), + jwks_uri: api_openid_connect_url, + scopes_supported: %w(openid read write), response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types, request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), subject_types_supported: %w(public pairwise), diff --git a/app/controllers/api/openid_connect/user_info_controller.rb b/app/controllers/api/openid_connect/user_info_controller.rb index 62de2389b..76b063ba5 100644 --- a/app/controllers/api/openid_connect/user_info_controller.rb +++ b/app/controllers/api/openid_connect/user_info_controller.rb @@ -4,7 +4,7 @@ module Api include Api::OpenidConnect::ProtectedResourceEndpoint before_action do - require_access_token Api::OpenidConnect::Scope.find_by(name: "openid") + require_access_token ["openid"] end def show diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index fce5220fe..a5a9ab3ed 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -7,9 +7,9 @@ module Api validates :user, presence: true validates :o_auth_application, presence: true validates :user, uniqueness: {scope: :o_auth_application} + validate :validate_scope_names + serialize :scopes, JSON - has_many :authorization_scopes - has_many :scopes, through: :authorization_scopes has_many :o_auth_access_tokens, dependent: :destroy has_many :id_tokens, dependent: :destroy @@ -21,21 +21,28 @@ module Api self.refresh_token = SecureRandom.hex(32) end - def accessible?(required_scopes=nil) - Array(required_scopes).all? do |required_scope| - scopes.include? required_scope + def validate_scope_names + return unless scopes + scopes.each do |scope| + errors.add(:scope, "is not a valid scope name") unless %w(openid read write).include? scope end end + def accessible?(required_scopes=nil) + Array(required_scopes).all? { |required_scope| + scopes.include? required_scope + } + end + def create_code - self.code = SecureRandom.hex(32) - save - code + SecureRandom.hex(32).tap do |code| + self.code = code + save + end end def create_access_token o_auth_access_tokens.create!.bearer_token - # TODO: Add support for request object end def create_id_token @@ -53,9 +60,12 @@ module Api end def self.use_code(code) - auth = find_by(code: code) - auth.code = nil if auth # Remove auth code if found so it can't be reused - auth + return unless code + find_by(code: code).tap do |auth| + return unless auth + auth.code = nil + auth.save + end end end end diff --git a/app/presenters/user_applications_presenter.rb b/app/presenters/user_applications_presenter.rb index 8c2d263fd..18cb63480 100644 --- a/app/presenters/user_applications_presenter.rb +++ b/app/presenters/user_applications_presenter.rb @@ -4,6 +4,7 @@ class UserApplicationsPresenter end def user_applications + # TODO: Fix and add tests @applications ||= @current_user.o_auth_applications.each_with_object([]) do |app, array| array << app_as_json(app) end @@ -29,9 +30,8 @@ class UserApplicationsPresenter end def find_scopes(application) - scopes = Api::OpenidConnect::Authorization.find_by_client_id_and_user( + Api::OpenidConnect::Authorization.find_by_client_id_and_user( application.client_id, @current_user).scopes - scopes.each_with_object([]) {|scope, array| array << scope.name } end def find_id(application) diff --git a/app/views/api/openid_connect/authorizations/new.html.haml b/app/views/api/openid_connect/authorizations/new.html.haml index 8009785ba..a20a3863b 100644 --- a/app/views/api/openid_connect/authorizations/new.html.haml +++ b/app/views/api/openid_connect/authorizations/new.html.haml @@ -4,7 +4,7 @@ = t(".with_id_token") %ul - @scopes.each do |scope| - %li= scope.name + %li= scope - if @request_object %li= t(".requested_objects") %ul diff --git a/db/migrate/20150708153926_create_authorizations.rb b/db/migrate/20150708153926_create_authorizations.rb index af659bcf0..07fc13002 100644 --- a/db/migrate/20150708153926_create_authorizations.rb +++ b/db/migrate/20150708153926_create_authorizations.rb @@ -7,6 +7,7 @@ class CreateAuthorizations < ActiveRecord::Migration t.string :code t.string :redirect_uri t.string :nonce + t.string :scopes t.timestamps null: false end diff --git a/db/migrate/20150708154727_create_scope.rb b/db/migrate/20150708154727_create_scope.rb deleted file mode 100644 index afdc320f6..000000000 --- a/db/migrate/20150708154727_create_scope.rb +++ /dev/null @@ -1,9 +0,0 @@ -class CreateScope < ActiveRecord::Migration - def change - create_table :scopes do |t| - t.primary_key :name, :string - - t.timestamps null: false - end - end -end diff --git a/db/migrate/20150708155202_create_authorization_scopes_join_table.rb b/db/migrate/20150708155202_create_authorization_scopes_join_table.rb deleted file mode 100644 index 79c275b41..000000000 --- a/db/migrate/20150708155202_create_authorization_scopes_join_table.rb +++ /dev/null @@ -1,8 +0,0 @@ -class CreateAuthorizationScopesJoinTable < ActiveRecord::Migration - def change - create_table :authorization_scopes, id: false do |t| - t.belongs_to :authorization, index: true - t.belongs_to :scope, index: true - end - end -end diff --git a/db/schema.rb b/db/schema.rb index fa60983f8..ac983f750 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -55,14 +55,6 @@ ActiveRecord::Schema.define(version: 20150801074555) do add_index "aspects", ["user_id", "contacts_visible"], name: "index_aspects_on_user_id_and_contacts_visible", using: :btree add_index "aspects", ["user_id"], name: "index_aspects_on_user_id", using: :btree - create_table "authorization_scopes", id: false, force: :cascade do |t| - t.integer "authorization_id", limit: 4 - t.integer "scope_id", limit: 4 - end - - add_index "authorization_scopes", ["authorization_id"], name: "index_authorization_scopes_on_authorization_id", using: :btree - add_index "authorization_scopes", ["scope_id"], name: "index_authorization_scopes_on_scope_id", using: :btree - create_table "authorizations", force: :cascade do |t| t.integer "user_id", limit: 4 t.integer "o_auth_application_id", limit: 4 @@ -70,6 +62,7 @@ ActiveRecord::Schema.define(version: 20150801074555) do t.string "code", limit: 255 t.string "redirect_uri", limit: 255 t.string "nonce", limit: 255 + t.string "scopes", limit: 255 t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -532,12 +525,6 @@ ActiveRecord::Schema.define(version: 20150801074555) do t.datetime "updated_at", null: false end - create_table "scopes", force: :cascade do |t| - t.string "name", limit: 255 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - create_table "services", force: :cascade do |t| t.string "type", limit: 127, null: false t.integer "user_id", limit: 4, null: false diff --git a/db/seeds.rb b/db/seeds.rb deleted file mode 100644 index 6ca70e345..000000000 --- a/db/seeds.rb +++ /dev/null @@ -1,3 +0,0 @@ -Api::OpenidConnect::Scope.find_or_create_by!(name: "openid") -Api::OpenidConnect::Scope.find_or_create_by!(name: "read") -Api::OpenidConnect::Scope.find_or_create_by!(name: "write") diff --git a/features/desktop/oidc_auth_code_flow.feature b/features/desktop/oidc_auth_code_flow.feature index 49dd10fe6..7a25bca72 100644 --- a/features/desktop/oidc_auth_code_flow.feature +++ b/features/desktop/oidc_auth_code_flow.feature @@ -2,7 +2,6 @@ Feature: Access protected resources using auth code flow Background: Given a user with username "kent" - And all scopes exist Scenario: Invalid client id to auth endpoint When I register a new client diff --git a/features/desktop/oidc_implicit_flow.feature b/features/desktop/oidc_implicit_flow.feature index 8ded34fd3..6d02919c1 100644 --- a/features/desktop/oidc_implicit_flow.feature +++ b/features/desktop/oidc_implicit_flow.feature @@ -2,7 +2,6 @@ Feature: Access protected resources using implicit flow Background: Given a user with username "kent" - And all scopes exist Scenario: Invalid client id to auth endpoint When I register a new client diff --git a/features/step_definitions/oidc_common_steps.rb b/features/step_definitions/oidc_common_steps.rb index f73d4407b..5c562f891 100644 --- a/features/step_definitions/oidc_common_steps.rb +++ b/features/step_definitions/oidc_common_steps.rb @@ -1,8 +1,3 @@ -Given(/^all scopes exist$/) do - Api::OpenidConnect::Scope.find_or_create_by(name: "openid") - Api::OpenidConnect::Scope.find_or_create_by(name: "read") -end - Given /^a client with a provided picture exists for user "([^\"]*)"$/ do |email| app = FactoryGirl.create(:o_auth_application_with_image) user = User.find_by(email: email) diff --git a/lib/api/openid_connect/authorization_point/endpoint.rb b/lib/api/openid_connect/authorization_point/endpoint.rb index 38ccb5f99..053686e92 100644 --- a/lib/api/openid_connect/authorization_point/endpoint.rb +++ b/lib/api/openid_connect/authorization_point/endpoint.rb @@ -44,9 +44,10 @@ module Api end def build_scopes(req) - @scopes = req.scope.map {|scope_name| - OpenidConnect::Scope.where(name: scope_name).first.tap do |scope| - req.invalid_scope! "Unknown scope: #{scope}" unless scope + @scopes = req.scope.map {|scope| + scope.tap do |scope_name| + # TODO: Use enum + req.invalid_scope! "Unknown scope: #{scope_name}" unless %w(openid read write).include? scope_name end } end diff --git a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb index 104b0a0fb..6a3670f8c 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -19,16 +19,23 @@ module Api end end - # TODO: Add support for request object + private + def approved!(req, res) - auth = OpenidConnect::Authorization.find_or_create_by( - o_auth_application: @o_auth_application, user: @user, redirect_uri: @redirect_uri) - auth.nonce = req.nonce - auth.scopes << @scopes unless auth.scopes == @scopes + auth = find_or_build_auth(req) handle_approved_response_type(auth, req, res) res.approve! end + def find_or_build_auth(req) + OpenidConnect::Authorization.find_or_create_by!( + o_auth_application: @o_auth_application, user: @user, redirect_uri: @redirect_uri).tap do |auth| + auth.nonce = req.nonce + auth.scopes = @scopes + auth.save + end + end + def handle_approved_response_type(auth, req, res) response_types = Array(req.response_type) handle_approved_auth_code(auth, res, response_types) diff --git a/lib/api/openid_connect/protected_resource_endpoint.rb b/lib/api/openid_connect/protected_resource_endpoint.rb index ca9f82ffe..a64c5db45 100644 --- a/lib/api/openid_connect/protected_resource_endpoint.rb +++ b/lib/api/openid_connect/protected_resource_endpoint.rb @@ -3,7 +3,7 @@ module Api module ProtectedResourceEndpoint attr_reader :current_token - def require_access_token(*required_scopes) + def require_access_token(required_scopes) @current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN] raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user") unless @current_token && @current_token.authorization diff --git a/lib/api/openid_connect/token_endpoint.rb b/lib/api/openid_connect/token_endpoint.rb index d71525e87..627dc6737 100644 --- a/lib/api/openid_connect/token_endpoint.rb +++ b/lib/api/openid_connect/token_endpoint.rb @@ -23,7 +23,7 @@ module Api auth = Api::OpenidConnect::Authorization.with_redirect_uri(req.redirect_uri).use_code(req.code) req.invalid_grant! if auth.blank? res.access_token = auth.create_access_token - if auth.accessible?(Api::OpenidConnect::Scope.find_by!(name: "openid")) + if auth.accessible? "openid" id_token = auth.create_id_token res.id_token = id_token.to_jwt(access_token: res.access_token) end @@ -32,16 +32,6 @@ module Api end end - def build_auth_and_access_token(auth, req, res) - scope_list = req.scope.map { |scope_name| - OpenidConnect::Scope.find_by(name: scope_name).tap do |scope| - req.invalid_scope! "Unknown scope: #{scope}" unless scope - end - } # TODO: Check client scope permissions - auth.scopes << scope_list - res.access_token = auth.create_access_token - end - def handle_refresh_flow(req, res) # Handle as if scope request was omitted even if provided. # See https://tools.ietf.org/html/rfc6749#section-6 for handling diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 5233ef976..fe81b3242 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -133,7 +133,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do context "when already authorized" do let!(:auth) { Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice, - redirect_uri: "http://localhost:3000/") + redirect_uri: "http://localhost:3000/", scopes: ["openid"]) } context "when valid parameters are passed" do diff --git a/spec/factories.rb b/spec/factories.rb index ce54a4aa4..772d36d43 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -336,32 +336,19 @@ FactoryGirl.define do factory :auth_with_read, class: Api::OpenidConnect::Authorization do o_auth_application user - - after(:create) do |auth_with_read| - auth_with_read.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "openid"), - Api::OpenidConnect::Scope.find_or_create_by(name: "read")] - end + scopes ["openid","read"] end factory :auth_with_read_and_ppid, class: Api::OpenidConnect::Authorization do association :o_auth_application, factory: :o_auth_application_with_ppid user - - after(:create) do |auth_with_read| - auth_with_read.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "openid"), - Api::OpenidConnect::Scope.find_or_create_by(name: "read")] - end + scopes ["openid","read"] end factory :auth_with_read_and_write, class: Api::OpenidConnect::Authorization do o_auth_application user - - after(:create) do |auth_with_read| - auth_with_read.scopes << [Api::OpenidConnect::Scope.find_or_create_by(name: "openid"), - Api::OpenidConnect::Scope.find_or_create_by(name: "read"), - Api::OpenidConnect::Scope.find_or_create_by(name: "write")] - end + scopes ["openid","read","write"] end # Factories for the DiasporaFederation-gem diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index dfbee1eeb..5f044d9ae 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -1,12 +1,10 @@ require "spec_helper" describe Api::OpenidConnect::TokenEndpoint, type: :request do let!(:client) { FactoryGirl.create(:o_auth_application_with_ppid) } - let!(:auth) do - auth = Api::OpenidConnect::Authorization.find_or_create_by( - o_auth_application: client, user: bob, redirect_uri: "http://localhost:3000/") - auth.scopes << [Api::OpenidConnect::Scope.find_by!(name: "openid")] - auth - end + let!(:auth) { + Api::OpenidConnect::Authorization.find_or_create_by( + o_auth_application: client, user: bob, redirect_uri: "http://localhost:3000/", scopes: ["openid"]) + } let!(:code) { auth.create_code } describe "the authorization code grant type" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c0a67974d..ca444256a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -84,7 +84,6 @@ RSpec.configure do |config| $process_queue = false allow_any_instance_of(Postzord::Dispatcher::Public).to receive(:deliver_to_remote) allow_any_instance_of(Postzord::Dispatcher::Private).to receive(:deliver_to_remote) - load "#{Rails.root}/db/seeds.rb" end config.expect_with :rspec do |expect_config| From 1a7f2edc013ebf05a1bac75bb21a62ece0e63981 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Wed, 12 Aug 2015 23:42:12 +0900 Subject: [PATCH 057/105] Perform major refactoring - Add foreign_keys - Remove unused classes/methods - Fix pronto errors - Add method to retrieve client id from name - Remove TODO comments - Fix unnecessary private key generation --- .gitignore | 2 +- .../authorizations_controller.rb | 14 +++--- .../api/openid_connect/clients_controller.rb | 9 ++++ app/controllers/api/v0/users_controller.rb | 13 ----- .../api/openid_connect/authorization.rb | 2 +- app/models/api/openid_connect/id_token.rb | 10 ++-- app/presenters/api/v0/base_presenter.rb | 6 --- app/presenters/user_applications_presenter.rb | 17 ++++--- .../authorizations/new.html.haml | 4 +- .../_add_remove_applications.haml | 2 +- config/locales/diaspora/en.yml | 14 +++--- config/routes.rb | 2 + ...150613202109_create_o_auth_applications.rb | 3 +- .../20150708153926_create_authorizations.rb | 2 + ...0708153928_create_o_auth_access_tokens.rb} | 3 +- db/migrate/20150714055110_create_id_tokens.rb | 1 + ...reate_pairwise_pseudonymous_identifiers.rb | 2 + db/schema.rb | 9 ++++ features/desktop/oidc_implicit_flow.feature | 2 +- features/desktop/user_applications.feature | 1 - features/mobile/user_applications.feature | 1 - .../step_definitions/implicit_flow_steps.rb | 47 +++++++++---------- .../authorization_point/endpoint.rb | 6 +-- .../endpoint_confirmation_point.rb | 4 +- .../endpoint_start_point.rb | 2 - lib/api/openid_connect/id_token_config.rb | 6 +-- .../authorizations_controller_spec.rb | 4 +- .../openid_connect/clients_controller_spec.rb | 22 +++++++++ .../discovery_controller_spec.rb | 9 ++-- .../api/v0/base_controller_spec.rb | 4 -- spec/factories.rb | 14 +++--- .../protected_resource_endpoint_spec.rb | 1 + .../api/openid_connect/token_endpoint_spec.rb | 17 +++++++ spec/presenters/api/v0/base_presenter_spec.rb | 4 -- 34 files changed, 143 insertions(+), 116 deletions(-) delete mode 100644 app/controllers/api/v0/users_controller.rb delete mode 100644 app/presenters/api/v0/base_presenter.rb rename db/migrate/{20150614134031_create_o_auth_access_tokens.rb => 20150708153928_create_o_auth_access_tokens.rb} (67%) delete mode 100644 spec/controllers/api/v0/base_controller_spec.rb delete mode 100644 spec/presenters/api/v0/base_presenter_spec.rb diff --git a/.gitignore b/.gitignore index 3847784e1..3fc25bd96 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ vendor/cache/ config/database.yml .rvmrc_custom .rvmrc.local -oidc_key.pem +config/oidc_key.pem # Mailing list stuff config/email_offset diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 5e6cc407c..de1aed192 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -30,7 +30,7 @@ module Api if authorization authorization.destroy else - raise ArgumentError, "Error while trying revoke non-existent authorization with ID #{params[:id]}" + flash[:error] = I18n.t("api.openid_connect.authorizations.destroy.fail", id: params[:id]) end redirect_to user_applications_url end @@ -54,9 +54,7 @@ module Api def reauthenticate sign_out current_user - params_as_get_query = params.map {|key, value| key.to_s + "=" + value }.join("&") - authorization_path_with_query = new_api_openid_connect_authorization_path + "?" + params_as_get_query - redirect_to authorization_path_with_query + redirect_to new_api_openid_connect_authorization_path(params) end def handle_authorization_form(auth) @@ -125,9 +123,9 @@ module Api @scopes.join(" ") end - def process_authorization_consent(approvedString) + def process_authorization_consent(approved_string) endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointConfirmationPoint.new( - current_user, to_boolean(approvedString)) + current_user, to_boolean(approved_string)) handle_confirmation_endpoint_response(endpoint) end @@ -166,7 +164,7 @@ module Api def response_type_as_space_seperated_values if session[:response_type].respond_to?(:map) - session[:response_type].map(&:to_s).join(" ") + session[:response_type].join(" ") else session[:response_type] end @@ -189,7 +187,7 @@ module Api def redirect_prompt_error_display(error, error_description) redirect_params_hash = {error: error, error_description: error_description, state: params[:state]} redirect_fragment = redirect_params_hash.compact.map {|key, value| key.to_s + "=" + value }.join("&") - redirect_to params[:redirect_uri] + "#" + redirect_fragment + redirect_to "#{params[:redirect_uri]}##{redirect_fragment}" end end end diff --git a/app/controllers/api/openid_connect/clients_controller.rb b/app/controllers/api/openid_connect/clients_controller.rb index 2ba6efdbe..28d34bae8 100644 --- a/app/controllers/api/openid_connect/clients_controller.rb +++ b/app/controllers/api/openid_connect/clients_controller.rb @@ -15,6 +15,15 @@ module Api render json: client.as_json(root: false) end + def find + client = Api::OpenidConnect::OAuthApplication.find_by(client_name: params[:client_name]) + if client + render json: {client_id: client.client_id} + else + render json: {error: "Client with name #{params[:client_name]} does not exist"} + end + end + private def http_error_page_as_json(e) diff --git a/app/controllers/api/v0/users_controller.rb b/app/controllers/api/v0/users_controller.rb deleted file mode 100644 index d2cdf9dfb..000000000 --- a/app/controllers/api/v0/users_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Api - module V0 - class UsersController < Api::V0::BaseController - before_action do - require_access_token Api::OpenidConnect::Scope.find_by(name: "read") - end - - def show - render json: current_user - end - end - end -end diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index a5a9ab3ed..32e0d52e3 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -62,7 +62,7 @@ module Api def self.use_code(code) return unless code find_by(code: code).tap do |auth| - return unless auth + next unless auth auth.code = nil auth.save end diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb index 5acfa0ea4..58f8dd453 100644 --- a/app/models/api/openid_connect/id_token.rb +++ b/app/models/api/openid_connect/id_token.rb @@ -16,10 +16,10 @@ module Api end def to_response_object(options={}) - id_token = OpenIDConnect::ResponseObject::IdToken.new(claims) - id_token.code = options[:code] if options[:code] - id_token.access_token = options[:access_token] if options[:access_token] - id_token + OpenIDConnect::ResponseObject::IdToken.new(claims).tap do |id_token| + id_token.code = options[:code] if options[:code] + id_token.access_token = options[:access_token] if options[:access_token] + end end def claims @@ -45,8 +45,6 @@ module Api authorization.user.diaspora_handle end end - - # TODO: Add support for request objects end end end diff --git a/app/presenters/api/v0/base_presenter.rb b/app/presenters/api/v0/base_presenter.rb deleted file mode 100644 index 18548cdd5..000000000 --- a/app/presenters/api/v0/base_presenter.rb +++ /dev/null @@ -1,6 +0,0 @@ -module Api - module V0 - class BasePresenter - end - end -end diff --git a/app/presenters/user_applications_presenter.rb b/app/presenters/user_applications_presenter.rb index 18cb63480..fe94a73b0 100644 --- a/app/presenters/user_applications_presenter.rb +++ b/app/presenters/user_applications_presenter.rb @@ -1,13 +1,10 @@ class UserApplicationsPresenter def initialize(user) - @current_user = user + @user = user end def user_applications - # TODO: Fix and add tests - @applications ||= @current_user.o_auth_applications.each_with_object([]) do |app, array| - array << app_as_json(app) - end + @applications ||= @user.o_auth_applications.map {|app| app_as_json(app) } end def applications_count @@ -30,12 +27,14 @@ class UserApplicationsPresenter end def find_scopes(application) - Api::OpenidConnect::Authorization.find_by_client_id_and_user( - application.client_id, @current_user).scopes + find_auth(application).scopes end def find_id(application) - Api::OpenidConnect::Authorization.find_by_client_id_and_user( - application.client_id, @current_user).id + find_auth(application).id + end + + def find_auth(application) + Api::OpenidConnect::Authorization.find_by_client_id_and_user(application.client_id, @user) end end diff --git a/app/views/api/openid_connect/authorizations/new.html.haml b/app/views/api/openid_connect/authorizations/new.html.haml index a20a3863b..001b066b9 100644 --- a/app/views/api/openid_connect/authorizations/new.html.haml +++ b/app/views/api/openid_connect/authorizations/new.html.haml @@ -1,7 +1,5 @@ %h2= @o_auth_application.client_name -%p= t(".will_be_redirected") -= @redirect_uri -= t(".with_id_token") +%p= t(".redirection_message", redirect_uri: @redirect_uri) %ul - @scopes.each do |scope| %li= scope diff --git a/app/views/user_applications/_add_remove_applications.haml b/app/views/user_applications/_add_remove_applications.haml index 9ca522541..ed50dba8c 100644 --- a/app/views/user_applications/_add_remove_applications.haml +++ b/app/views/user_applications/_add_remove_applications.haml @@ -9,7 +9,7 @@ %i.entypo-browser .application-authorizations - if app[:authorizations].count > 0 - %h4="#{app[:name]} #{t("user_applications.index.access")}" + %h4=t("user_applications.index.access", name: app[:name]) %ul - app[:authorizations].each do |authorization| %li diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 3bebdaf2c..b259e1939 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -885,12 +885,12 @@ en: openid_connect: authorizations: new: - will_be_redirected: "You will be redirected to" - with_id_token: "with an id token if approved or an error if denied" - requested_objects: "Request Objects (Currently not supported)" + redirection_message: "Are you sure you want to give access to %{redirect_uri}?" form: approve: "Approve" deny: "Deny" + destroy: + fail: "The attempt to revoke the authorization with ID %{id} has failed" people: zero: "No people" one: "1 person" @@ -1480,11 +1480,11 @@ en: user_applications: index: edit_applications: "Applications" - title: "Your authorized applications" - access: "is authorized access to:" + title: "Authorized applications" + access: "%{name} is authorized access to:" no_requirement: "This application requires no permissions" - applications_explanation: "Here is a list of applications that you to which have authorized your profile information" - no_applications: "You have no authorized applications for now" + applications_explanation: "Here is a list of applications to which you have authorized access" + no_applications: "You have no authorized applications" revoke_autorization: "Revoke" scopes: openid: diff --git a/config/routes.rb b/config/routes.rb index a92555038..79e585e97 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -240,6 +240,8 @@ Diaspora::Application.routes.draw do namespace :api do namespace :openid_connect do resources :clients, only: :create + get "clients/find", to: "clients#find" + post "access_tokens", to: proc {|env| Api::OpenidConnect::TokenEndpoint.new.call(env) } # Authorization Servers MUST support the use of the HTTP GET and POST methods at the Authorization Endpoint diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index 874122eb2..1e7cf290f 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -2,7 +2,7 @@ class CreateOAuthApplications < ActiveRecord::Migration def change create_table :o_auth_applications do |t| t.belongs_to :user, index: true - t.string :client_id + t.string :client_id, index: {unique: true, length: 191} t.string :client_secret t.string :client_name @@ -20,5 +20,6 @@ class CreateOAuthApplications < ActiveRecord::Migration t.timestamps null: false end + add_foreign_key :o_auth_applications, :users end end diff --git a/db/migrate/20150708153926_create_authorizations.rb b/db/migrate/20150708153926_create_authorizations.rb index 07fc13002..f964237d7 100644 --- a/db/migrate/20150708153926_create_authorizations.rb +++ b/db/migrate/20150708153926_create_authorizations.rb @@ -11,5 +11,7 @@ class CreateAuthorizations < ActiveRecord::Migration t.timestamps null: false end + add_foreign_key :authorizations, :users + add_foreign_key :authorizations, :o_auth_applications end end diff --git a/db/migrate/20150614134031_create_o_auth_access_tokens.rb b/db/migrate/20150708153928_create_o_auth_access_tokens.rb similarity index 67% rename from db/migrate/20150614134031_create_o_auth_access_tokens.rb rename to db/migrate/20150708153928_create_o_auth_access_tokens.rb index 8bd619142..5172ff7c6 100644 --- a/db/migrate/20150614134031_create_o_auth_access_tokens.rb +++ b/db/migrate/20150708153928_create_o_auth_access_tokens.rb @@ -2,10 +2,11 @@ class CreateOAuthAccessTokens < ActiveRecord::Migration def change create_table :o_auth_access_tokens do |t| t.belongs_to :authorization, index: true - t.string :token + t.string :token, index: {unique: true, length: 191} t.datetime :expires_at t.timestamps null: false end + add_foreign_key :o_auth_access_tokens, :authorizations end end diff --git a/db/migrate/20150714055110_create_id_tokens.rb b/db/migrate/20150714055110_create_id_tokens.rb index 19887d252..4a5af5985 100644 --- a/db/migrate/20150714055110_create_id_tokens.rb +++ b/db/migrate/20150714055110_create_id_tokens.rb @@ -7,5 +7,6 @@ class CreateIdTokens < ActiveRecord::Migration t.timestamps null: false end + add_foreign_key :id_tokens, :authorizations end end diff --git a/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb b/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb index 125b95bfc..3b346dda0 100644 --- a/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb +++ b/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb @@ -7,5 +7,7 @@ class CreatePairwisePseudonymousIdentifiers < ActiveRecord::Migration t.primary_key :guid, :string, limit: 32 t.string :sector_identifier end + add_foreign_key :ppid, :o_auth_applications + add_foreign_key :ppid, :users end end diff --git a/db/schema.rb b/db/schema.rb index ac983f750..084e882a6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -270,6 +270,7 @@ ActiveRecord::Schema.define(version: 20150801074555) do end add_index "o_auth_access_tokens", ["authorization_id"], name: "index_o_auth_access_tokens_on_authorization_id", using: :btree + add_index "o_auth_access_tokens", ["token"], name: "index_o_auth_access_tokens_on_token", unique: true, length: {"token"=>191}, using: :btree create_table "o_auth_applications", force: :cascade do |t| t.integer "user_id", limit: 4 @@ -291,6 +292,7 @@ ActiveRecord::Schema.define(version: 20150801074555) do t.datetime "updated_at", null: false end + add_index "o_auth_applications", ["client_id"], name: "index_o_auth_applications_on_client_id", unique: true, length: {"client_id"=>191}, using: :btree add_index "o_auth_applications", ["user_id"], name: "index_o_auth_applications_on_user_id", using: :btree create_table "o_embed_caches", force: :cascade do |t| @@ -656,18 +658,25 @@ ActiveRecord::Schema.define(version: 20150801074555) do add_foreign_key "aspect_memberships", "aspects", name: "aspect_memberships_aspect_id_fk", on_delete: :cascade add_foreign_key "aspect_memberships", "contacts", name: "aspect_memberships_contact_id_fk", on_delete: :cascade add_foreign_key "aspect_visibilities", "aspects", name: "aspect_visibilities_aspect_id_fk", on_delete: :cascade + add_foreign_key "authorizations", "o_auth_applications" + add_foreign_key "authorizations", "users" add_foreign_key "comments", "people", column: "author_id", name: "comments_author_id_fk", on_delete: :cascade add_foreign_key "contacts", "people", name: "contacts_person_id_fk", on_delete: :cascade add_foreign_key "conversation_visibilities", "conversations", name: "conversation_visibilities_conversation_id_fk", on_delete: :cascade add_foreign_key "conversation_visibilities", "people", name: "conversation_visibilities_person_id_fk", on_delete: :cascade add_foreign_key "conversations", "people", column: "author_id", name: "conversations_author_id_fk", on_delete: :cascade + add_foreign_key "id_tokens", "authorizations" add_foreign_key "invitations", "users", column: "recipient_id", name: "invitations_recipient_id_fk", on_delete: :cascade add_foreign_key "invitations", "users", column: "sender_id", name: "invitations_sender_id_fk", on_delete: :cascade add_foreign_key "likes", "people", column: "author_id", name: "likes_author_id_fk", on_delete: :cascade add_foreign_key "messages", "conversations", name: "messages_conversation_id_fk", on_delete: :cascade add_foreign_key "messages", "people", column: "author_id", name: "messages_author_id_fk", on_delete: :cascade add_foreign_key "notification_actors", "notifications", name: "notification_actors_notification_id_fk", on_delete: :cascade + add_foreign_key "o_auth_access_tokens", "authorizations" + add_foreign_key "o_auth_applications", "users" add_foreign_key "posts", "people", column: "author_id", name: "posts_author_id_fk", on_delete: :cascade + add_foreign_key "ppid", "o_auth_applications" + add_foreign_key "ppid", "users" add_foreign_key "profiles", "people", name: "profiles_person_id_fk", on_delete: :cascade add_foreign_key "services", "users", name: "services_user_id_fk", on_delete: :cascade add_foreign_key "share_visibilities", "contacts", name: "post_visibilities_contact_id_fk", on_delete: :cascade diff --git a/features/desktop/oidc_implicit_flow.feature b/features/desktop/oidc_implicit_flow.feature index 6d02919c1..15565ac20 100644 --- a/features/desktop/oidc_implicit_flow.feature +++ b/features/desktop/oidc_implicit_flow.feature @@ -27,7 +27,7 @@ Feature: Access protected resources using implicit flow Scenario: Application is authorized and uses small value for the max_age parameter When I register a new client And I sign in as "kent@kent.kent" - And I pass time + And I have signed in 5 minutes ago And I send a post request from that client to the authorization endpoint with max age And I sign in as "kent@kent.kent" And I give my consent and authorize the client diff --git a/features/desktop/user_applications.feature b/features/desktop/user_applications.feature index 99a2f8843..b1147ae1a 100644 --- a/features/desktop/user_applications.feature +++ b/features/desktop/user_applications.feature @@ -4,7 +4,6 @@ Feature: managing authorized applications Given following users exist: | username | email | | Augier | augier@example.org | - And all scopes exist And a client with a provided picture exists for user "augier@example.org" And a client exists for user "augier@example.org" diff --git a/features/mobile/user_applications.feature b/features/mobile/user_applications.feature index 18895e3ce..9e7ecbe2f 100644 --- a/features/mobile/user_applications.feature +++ b/features/mobile/user_applications.feature @@ -5,7 +5,6 @@ Feature: managing authorized applications Given following users exist: | username | email | | Augier | augier@example.org | - And all scopes exist And a client with a provided picture exists for user "augier@example.org" And a client exists for user "augier@example.org" diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb index 5e07969bf..bbbc0462a 100644 --- a/features/step_definitions/implicit_flow_steps.rb +++ b/features/step_definitions/implicit_flow_steps.rb @@ -1,40 +1,39 @@ -o_auth_query_params = %i( - redirect_uri=http://localhost:3000 - response_type=id_token%20token - scope=openid%20read - nonce=hello - state=hi - prompt=login -).join("&") +O_AUTH_QUERY_PARAMS = { + redirect_uri: "http://localhost:3000", + response_type: "id_token token", + scope: "openid read", + nonce: "hello", + state: "hi", + prompt: "login" +} -o_auth_query_params_with_max_age = %i( - redirect_uri=http://localhost:3000 - response_type=id_token%20token - scope=openid%20read - nonce=hello - state=hi - prompt=login - max_age=30 -).join("&") +O_AUTH_QUERY_PARAMS_WITH_MAX_AGE = { + redirect_uri: "http://localhost:3000", + response_type: "id_token token", + scope: "openid read", + nonce: "hello", + state: "hi", + prompt: "login", + max_age: 30 +} Given /^I send a post request from that client to the authorization endpoint$/ do client_json = JSON.parse(last_response.body) - visit new_api_openid_connect_authorization_path + - "?client_id=#{client_json['client_id']}&#{o_auth_query_params}" + visit new_api_openid_connect_authorization_path(O_AUTH_QUERY_PARAMS.merge(client_id: client_json["client_id"])) end -Given /^I pass time$/ do - Timecop.travel(Time.zone.now + 1.minute) +Given /^I have signed in (\d+) minutes ago$/ do |minutes| + @me.update_attribute(:current_sign_in_at, Time.zone.now - minutes.to_i.minute) end Given /^I send a post request from that client to the authorization endpoint with max age$/ do client_json = JSON.parse(last_response.body) - visit new_api_openid_connect_authorization_path + - "?client_id=#{client_json['client_id']}&#{o_auth_query_params_with_max_age}" + visit new_api_openid_connect_authorization_path( + O_AUTH_QUERY_PARAMS_WITH_MAX_AGE.merge(client_id: client_json["client_id"])) end Given /^I send a post request from that client to the authorization endpoint using a invalid client id$/ do - visit new_api_openid_connect_authorization_path + "?client_id=randomid&#{o_auth_query_params}" + visit new_api_openid_connect_authorization_path(O_AUTH_QUERY_PARAMS.merge(client_id: "randomid")) end When /^I give my consent and authorize the client$/ do diff --git a/lib/api/openid_connect/authorization_point/endpoint.rb b/lib/api/openid_connect/authorization_point/endpoint.rb index 053686e92..bfc5d5ea4 100644 --- a/lib/api/openid_connect/authorization_point/endpoint.rb +++ b/lib/api/openid_connect/authorization_point/endpoint.rb @@ -6,8 +6,8 @@ module Api :scopes, :_request_, :request_uri, :request_object, :nonce delegate :call, to: :app - def initialize(current_user) - @user = current_user + def initialize(user) + @user = user @app = Rack::OAuth2::Server::Authorize.new do |req, res| build_attributes(req, res) if OAuthApplication.available_response_types.include? Array(req.response_type).map(&:to_s).join(" ") @@ -26,7 +26,7 @@ module Api end def handle_response_type(_req, _res) - # Implemented by subclass + raise NotImplementedError # Implemented by subclass end private diff --git a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb index 6a3670f8c..dbb9abca5 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -56,9 +56,7 @@ module Api def handle_approved_id_token(auth, res, response_types) return unless response_types.include?(:id_token) id_token = auth.create_id_token - auth_code_value = res.respond_to?(:code) ? res.code : nil - access_token_value = res.respond_to?(:access_token) ? res.access_token : nil - res.id_token = id_token.to_jwt(code: auth_code_value, access_token: access_token_value) + res.id_token = id_token.to_jwt(code: res.try(:code), access_token: res.try(:access_token)) end end end diff --git a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb index 69667cd0e..65cdaa1a3 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb @@ -5,8 +5,6 @@ module Api def handle_response_type(req, _res) @response_type = req.response_type end - - # TODO: buildRequestObject(req) end end end diff --git a/lib/api/openid_connect/id_token_config.rb b/lib/api/openid_connect/id_token_config.rb index 541fa4663..7592e105f 100644 --- a/lib/api/openid_connect/id_token_config.rb +++ b/lib/api/openid_connect/id_token_config.rb @@ -1,14 +1,12 @@ module Api module OpenidConnect class IdTokenConfig - private_key = OpenSSL::PKey::RSA.new(2048) key_file_path = File.join(Rails.root, "config", "oidc_key.pem") if File.exist?(key_file_path) private_key = OpenSSL::PKey::RSA.new(File.read(key_file_path)) else - open key_file_path, "w" do |io| - io.write private_key.to_pem - end + private_key = OpenSSL::PKey::RSA.new(2048) + File.write key_file_path, private_key.to_pem File.chmod(0600, key_file_path) end PRIVATE_KEY = private_key diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index fe81b3242..aa4f13a98 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -301,7 +301,9 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do context "with non-existent authorization" do it "raises an error" do - expect { delete :destroy, id: 123_456_789 }.to raise_error(ArgumentError) + delete :destroy, id: 123_456_789 + expect(response).to redirect_to(user_applications_url) + expect(flash[:error]).to eq("The attempt to revoke the authorization with ID 123456789 has failed") end end end diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index 48204c86b..159a0059f 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -14,6 +14,7 @@ describe Api::OpenidConnect::ClientsController, type: :controller do expect(client_json["ppid"]).to eq(true) end end + context "when redirect uri is missing" do it "should return a invalid_client_metadata error" do post :create, response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -23,6 +24,7 @@ describe Api::OpenidConnect::ClientsController, type: :controller do expect(client_json["error"]).to have_content("invalid_client_metadata") end end + context "when redirect client_name is missing" do it "should return a invalid_client_metadata error" do post :create, redirect_uris: ["http://localhost"], response_types: [], grant_types: [], @@ -34,4 +36,24 @@ describe Api::OpenidConnect::ClientsController, type: :controller do end end end + + describe "#find" do + let!(:client) { FactoryGirl.create(:o_auth_application) } + + context "when an OIDC client already exists" do + it "should return a client id" do + get :find, client_name: client.client_name + client_id_json = JSON.parse(response.body) + expect(client_id_json["client_id"]).to eq(client.client_id) + end + end + + context "when an OIDC client doesn't already exist" do + it "should return the appropriate error" do + get :find, client_name: "random_name" + client_id_json = JSON.parse(response.body) + expect(client_id_json["error"]).to eq("Client with name random_name does not exist") + end + end + end end diff --git a/spec/controllers/api/openid_connect/discovery_controller_spec.rb b/spec/controllers/api/openid_connect/discovery_controller_spec.rb index 77de08ea0..19b90d6c5 100644 --- a/spec/controllers/api/openid_connect/discovery_controller_spec.rb +++ b/spec/controllers/api/openid_connect/discovery_controller_spec.rb @@ -3,17 +3,17 @@ require "spec_helper" describe Api::OpenidConnect::DiscoveryController, type: :controller do describe "#webfinger" do before do - get :webfinger, resource: "http://test.host/bob" + get :webfinger, resource: "http://example.com/bob" end it "should return a url to the openid-configuration" do json_body = JSON.parse(response.body) - expect(json_body["links"].first["href"]).to eq("http://test.host/") + expect(json_body["links"].first["href"]).to eq(root_url) end it "should return the resource in the subject" do json_body = JSON.parse(response.body) - expect(json_body["subject"]).to eq("http://test.host/bob") + expect(json_body["subject"]).to eq("http://example.com/bob") end end @@ -21,9 +21,10 @@ describe Api::OpenidConnect::DiscoveryController, type: :controller do before do get :configuration end + it "should have the issuer as the root url" do json_body = JSON.parse(response.body) - expect(json_body["issuer"]).to eq("http://test.host/") + expect(json_body["issuer"]).to eq(root_url) end it "should have the appropriate user info endpoint" do diff --git a/spec/controllers/api/v0/base_controller_spec.rb b/spec/controllers/api/v0/base_controller_spec.rb deleted file mode 100644 index dc359bbe2..000000000 --- a/spec/controllers/api/v0/base_controller_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require "spec_helper" - -describe Api::V0::BaseController do -end diff --git a/spec/factories.rb b/spec/factories.rb index 772d36d43..0e7493360 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -312,43 +312,43 @@ FactoryGirl.define do factory :o_auth_application, class: Api::OpenidConnect::OAuthApplication do client_name "Diaspora Test Client" - redirect_uris ["http://localhost:3000/"] + redirect_uris %w(http://localhost:3000/) end factory :o_auth_application_with_image, class: Api::OpenidConnect::OAuthApplication do client_name "Diaspora Test Client" - redirect_uris ["http://localhost:3000/"] + redirect_uris %w(http://localhost:3000/) logo_uri "/assets/user/default.png" end factory :o_auth_application_with_ppid, class: Api::OpenidConnect::OAuthApplication do client_name "Diaspora Test Client" - redirect_uris ["http://localhost:3000/"] + redirect_uris %w(http://localhost:3000/) ppid true sector_identifier_uri "https://example.com/uri" end factory :o_auth_application_with_multiple_redirects, class: Api::OpenidConnect::OAuthApplication do client_name "Diaspora Test Client" - redirect_uris ["http://localhost:3000/", "http://localhost/"] + redirect_uris %w(http://localhost:3000/ http://localhost/) end factory :auth_with_read, class: Api::OpenidConnect::Authorization do o_auth_application user - scopes ["openid","read"] + scopes %w(openid read) end factory :auth_with_read_and_ppid, class: Api::OpenidConnect::Authorization do association :o_auth_application, factory: :o_auth_application_with_ppid user - scopes ["openid","read"] + scopes %w(openid read) end factory :auth_with_read_and_write, class: Api::OpenidConnect::Authorization do o_auth_application user - scopes ["openid","read","write"] + scopes %w(openid read write) end # Factories for the DiasporaFederation-gem diff --git a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb index 0d1f9eaa3..4c9035ece 100644 --- a/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/protected_resource_endpoint_spec.rb @@ -1,4 +1,5 @@ require "spec_helper" + describe Api::OpenidConnect::ProtectedResourceEndpoint, type: :request do let(:auth_with_read) { FactoryGirl.create(:auth_with_read) } let!(:access_token_with_read) { auth_with_read.create_access_token.to_s } diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index 5f044d9ae..a3e0e20c6 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -1,4 +1,5 @@ require "spec_helper" + describe Api::OpenidConnect::TokenEndpoint, type: :request do let!(:client) { FactoryGirl.create(:o_auth_application_with_ppid) } let!(:auth) { @@ -34,6 +35,22 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do access_token_check_num = UrlSafeBase64.encode64(OpenSSL::Digest::SHA256.digest(access_token)[0, 128 / 8]) expect(decoded_token.at_hash).to eq(access_token_check_num) end + + it "should not allow code to be reused" do + auth.reload + expect(auth.code).to eq(nil) + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + client_id: client.client_id, client_secret: client.client_secret, + redirect_uri: "http://localhost:3000/", code: code + expect(JSON.parse(response.body)["error"]).to eq("invalid_grant") + end + + it "should not allow a nil code" do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + client_id: client.client_id, client_secret: client.client_secret, + redirect_uri: "http://localhost:3000/", code: nil + expect(JSON.parse(response.body)["error"]).to eq("invalid_request") + end end context "when the authorization code is not valid" do diff --git a/spec/presenters/api/v0/base_presenter_spec.rb b/spec/presenters/api/v0/base_presenter_spec.rb deleted file mode 100644 index 01261461a..000000000 --- a/spec/presenters/api/v0/base_presenter_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require "spec_helper" - -describe Api::V0::BasePresenter do -end From c33cce09536cf6a91ecf5ae9e943bb7681e23816 Mon Sep 17 00:00:00 2001 From: augier Date: Sun, 16 Aug 2015 12:35:25 +0200 Subject: [PATCH 058/105] Styling user consent form --- app/assets/stylesheets/mobile/settings.scss | 33 +----------- app/assets/stylesheets/user_applications.scss | 52 +++++++++---------- .../authorizations_controller.rb | 9 +++- .../user_applications_controller.rb | 11 ++++ .../user_applications_controller.rb | 9 ---- .../api/openid_connect/authorization.rb | 9 +++- .../openid_connect/authorizations/_form.haml | 7 --- .../authorizations/_grants_list.haml | 16 ++++++ .../authorizations/new.html.haml | 23 ++++---- .../_add_remove_applications.haml | 14 +++++ .../user_applications/_grants_list.haml | 16 ++++++ .../user_applications/index.html.haml | 0 .../user_applications/index.mobile.haml | 13 +++++ app/views/shared/_settings_nav.haml | 3 +- app/views/shared/_settings_nav.mobile.haml | 2 +- .../_add_remove_applications.haml | 28 ---------- app/views/user_applications/index.mobile.haml | 13 ----- config/locales/diaspora/en.yml | 50 +++++++++--------- config/routes.rb | 4 +- features/support/paths.rb | 2 +- .../authorization_point/endpoint.rb | 9 ++-- .../authorizations_controller_spec.rb | 2 +- 22 files changed, 160 insertions(+), 165 deletions(-) create mode 100644 app/controllers/api/openid_connect/user_applications_controller.rb delete mode 100644 app/controllers/user_applications_controller.rb delete mode 100644 app/views/api/openid_connect/authorizations/_form.haml create mode 100644 app/views/api/openid_connect/authorizations/_grants_list.haml create mode 100644 app/views/api/openid_connect/user_applications/_add_remove_applications.haml create mode 100644 app/views/api/openid_connect/user_applications/_grants_list.haml rename app/views/{ => api/openid_connect}/user_applications/index.html.haml (100%) create mode 100644 app/views/api/openid_connect/user_applications/index.mobile.haml delete mode 100644 app/views/user_applications/_add_remove_applications.haml delete mode 100644 app/views/user_applications/index.mobile.haml diff --git a/app/assets/stylesheets/mobile/settings.scss b/app/assets/stylesheets/mobile/settings.scss index 9b614aa66..45b7c633e 100644 --- a/app/assets/stylesheets/mobile/settings.scss +++ b/app/assets/stylesheets/mobile/settings.scss @@ -30,35 +30,6 @@ } } -.applications-page { - .applications-explenation { - margin-bottom: 15px; - } - - .application-img { - margin: 9px 0; - float: left; - width: 60px; - max-height: 60px; - text-align: center; - - [class^="entypo-"] { - font-size: 60px; - height: 60px; - margin: 0; - padding: 0; - width: 100%; - &::before { - position: relative; - top: -15px; - } - } - } - - .application-authorizations { - width: calc(100% - 60px); - padding: 0 0 15px 15px; - display: inline-block; - float: right; - } +.applications-page .applications-explanation { + margin-bottom: 15px; } diff --git a/app/assets/stylesheets/user_applications.scss b/app/assets/stylesheets/user_applications.scss index c99ed3672..e3cb69436 100644 --- a/app/assets/stylesheets/user_applications.scss +++ b/app/assets/stylesheets/user_applications.scss @@ -1,32 +1,28 @@ -.applications-page { - .applications-explenation { - margin-bottom: 15px; - } +.application-img { + margin: 9px 0; + float: left; + width: 60px; + max-height: 60px; + text-align: center; - .application-img { - margin: 9px 0; - float: left; - width: 60px; - max-height: 60px; - text-align: center; - - [class^="entypo-"] { - font-size: 60px; - height: 60px; - margin: 0; - padding: 0; - width: 100%; - &::before { - position: relative; - top: -15px; - } + [class^="entypo-"] { + font-size: 60px; + height: 60px; + margin: 0; + padding: 0; + width: 100%; + &::before { + position: relative; + top: -15px; } } - - .application-authorizations { - width: calc(100% - 60px); - padding: 0 0 15px 15px; - display: inline-block; - float: right; - } } + +.application-authorizations { + width: calc(100% - 60px); + padding: 0 0 15px 15px; + display: inline-block; + float: right; +} + +.user-consent { margin-top: 20px; } diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index de1aed192..09d479f74 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -32,7 +32,7 @@ module Api else flash[:error] = I18n.t("api.openid_connect.authorizations.destroy.fail", id: params[:id]) end - redirect_to user_applications_url + redirect_to api_openid_connect_user_applications_url end private @@ -107,6 +107,13 @@ module Api endpoint.redirect_uri, endpoint.scopes, endpoint.request_object ] save_request_parameters + + @app = { + name: @o_auth_application.client_name, + image: @o_auth_application.image_uri, + authorizations: @scopes + } + render :new end diff --git a/app/controllers/api/openid_connect/user_applications_controller.rb b/app/controllers/api/openid_connect/user_applications_controller.rb new file mode 100644 index 000000000..f25603a52 --- /dev/null +++ b/app/controllers/api/openid_connect/user_applications_controller.rb @@ -0,0 +1,11 @@ +module Api + module OpenidConnect + class UserApplicationsController < ApplicationController + before_action :authenticate_user! + + def index + @user_apps = UserApplicationsPresenter.new current_user + end + end + end +end diff --git a/app/controllers/user_applications_controller.rb b/app/controllers/user_applications_controller.rb deleted file mode 100644 index 35741d02b..000000000 --- a/app/controllers/user_applications_controller.rb +++ /dev/null @@ -1,9 +0,0 @@ -class UserApplicationsController < ApplicationController - before_action :authenticate_user! - - def index - respond_to do |format| - format.all { @user_apps = UserApplicationsPresenter.new current_user } - end - end -end diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index 32e0d52e3..2cdfb1e6d 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -21,10 +21,11 @@ module Api self.refresh_token = SecureRandom.hex(32) end + def validate_scope_names return unless scopes scopes.each do |scope| - errors.add(:scope, "is not a valid scope name") unless %w(openid read write).include? scope + errors.add(:scope, "is not a valid scope name") unless scopes.include? scope end end @@ -56,9 +57,13 @@ module Api def self.find_by_refresh_token(client_id, refresh_token) Api::OpenidConnect::Authorization.joins(:o_auth_application).find_by( - o_auth_applications: {client_id: client_id}, refresh_token: refresh_token) + o_auth_applications: {client_id: client_id}, refresh_token: refresh_token) end + def self.scopes + %w(openid read write) + end + def self.use_code(code) return unless code find_by(code: code).tap do |auth| diff --git a/app/views/api/openid_connect/authorizations/_form.haml b/app/views/api/openid_connect/authorizations/_form.haml deleted file mode 100644 index 0e1662d12..000000000 --- a/app/views/api/openid_connect/authorizations/_form.haml +++ /dev/null @@ -1,7 +0,0 @@ -= form_tag api_openid_connect_authorizations_path, class: action do - - if action == :approve - = submit_tag t(".approve") - = hidden_field_tag :approve, true - - else - = submit_tag t(".deny") - = hidden_field_tag :approve, false diff --git a/app/views/api/openid_connect/authorizations/_grants_list.haml b/app/views/api/openid_connect/authorizations/_grants_list.haml new file mode 100644 index 000000000..022304303 --- /dev/null +++ b/app/views/api/openid_connect/authorizations/_grants_list.haml @@ -0,0 +1,16 @@ +.application-img + - if app[:image] + = image_tag app[:image], class: "img-responsive" + - else + %i.entypo-browser +.application-authorizations + - if app[:authorizations].count > 0 + %h4=t("api.openid_connect.authorizations.new.access", name: app[:name]) + %ul + - app[:authorizations].each do |authorization| + %li + %b= t("api.openid_connect.scopes.#{authorization}.name") + %p= t("api.openid_connect.scopes.#{authorization}.description") + - else + .well + =t("api.openid_connect.authorizations.new.no_requirement", name: app[:name]) diff --git a/app/views/api/openid_connect/authorizations/new.html.haml b/app/views/api/openid_connect/authorizations/new.html.haml index 001b066b9..4443cba3b 100644 --- a/app/views/api/openid_connect/authorizations/new.html.haml +++ b/app/views/api/openid_connect/authorizations/new.html.haml @@ -1,12 +1,13 @@ -%h2= @o_auth_application.client_name -%p= t(".redirection_message", redirect_uri: @redirect_uri) -%ul - - @scopes.each do |scope| - %li= scope - - if @request_object - %li= t(".requested_objects") - %ul - %pre= JSON.pretty_generate @request_object.as_json +.user-consent.col-md-6.col-md-offset-1 + %ul.list-group + %li.list-group-item.authorized-application + = render "grants_list", app: @app -= render 'api/openid_connect/authorizations/form', action: :approve -= render 'api/openid_connect/authorizations/form', action: :deny + .clearfix + = form_tag api_openid_connect_authorizations_path, class: "pull-right" do + %span + = submit_tag t(".deny"), class: "btn btn-danger" + = hidden_field_tag :deny, false + %span + = submit_tag t(".approve"), class: "btn btn-primary" + = hidden_field_tag :approve, true diff --git a/app/views/api/openid_connect/user_applications/_add_remove_applications.haml b/app/views/api/openid_connect/user_applications/_add_remove_applications.haml new file mode 100644 index 000000000..28afb9021 --- /dev/null +++ b/app/views/api/openid_connect/user_applications/_add_remove_applications.haml @@ -0,0 +1,14 @@ +- if @user_apps.applications? + %ul.list-group + - @user_apps.user_applications.each do |app| + %li.list-group-item.authorized-application + = render "grants_list", app: app + = form_for "application", url: "#{api_openid_connect_authorizations_path}/#{app[:id]}", + html: { method: :delete, class: "form-horizontal"} do |f| + .clearfix= f.submit t("api.openid_connect.user_applications.revoke_autorization"), + class: "btn btn-danger pull-right app-revoke" + +- else + .well + %h4 + = t("api.openid_connect.user_applications.no_applications") diff --git a/app/views/api/openid_connect/user_applications/_grants_list.haml b/app/views/api/openid_connect/user_applications/_grants_list.haml new file mode 100644 index 000000000..e2c67b8ff --- /dev/null +++ b/app/views/api/openid_connect/user_applications/_grants_list.haml @@ -0,0 +1,16 @@ +.application-img + - if app[:image] + = image_tag app[:image], class: "img-responsive" + - else + %i.entypo-browser +.application-authorizations + - if app[:authorizations].count > 0 + %h4=t("api.openid_connect.user_applications.index.access", name: app[:name]) + %ul + - app[:authorizations].each do |authorization| + %li + %b= t("api.openid_connect.scopes.#{authorization}.name") + %p= t("api.openid_connect.scopes.#{authorization}.description") + - else + .well + =t("api.openid_connect.user_applications.index.no_requirement",name: app[:name]) diff --git a/app/views/user_applications/index.html.haml b/app/views/api/openid_connect/user_applications/index.html.haml similarity index 100% rename from app/views/user_applications/index.html.haml rename to app/views/api/openid_connect/user_applications/index.html.haml diff --git a/app/views/api/openid_connect/user_applications/index.mobile.haml b/app/views/api/openid_connect/user_applications/index.mobile.haml new file mode 100644 index 000000000..da89efde1 --- /dev/null +++ b/app/views/api/openid_connect/user_applications/index.mobile.haml @@ -0,0 +1,13 @@ +.settings_container.applications-page + - content_for :page_title do + = t(".edit_applications") + + = render "shared/settings_nav" + + .container-fluid + .row + .col-md-12.applications-explanation + = t(".applications_explanation") + .col-md-12 + = render "add_remove_applications" + diff --git a/app/views/shared/_settings_nav.haml b/app/views/shared/_settings_nav.haml index 8bd78cc53..ceba4baa0 100644 --- a/app/views/shared/_settings_nav.haml +++ b/app/views/shared/_settings_nav.haml @@ -6,4 +6,5 @@ %li{class: current_page?(edit_user_path) && "active"}= link_to t("account"), edit_user_path %li{class: current_page?(privacy_settings_path) && "active"}= link_to t("privacy"), privacy_settings_path %li{class: current_page?(services_path) && "active"}= link_to t("_services"), services_path - %li{class: current_page?(user_applications_path) && 'active'}= link_to t("_applications"), user_applications_path + %li{class: current_page?(api_openid_connect_user_applications_path) && "active"} + = link_to t("_applications"), api_openid_connect_user_applications_path diff --git a/app/views/shared/_settings_nav.mobile.haml b/app/views/shared/_settings_nav.mobile.haml index eff5ef101..a85537dcb 100644 --- a/app/views/shared/_settings_nav.mobile.haml +++ b/app/views/shared/_settings_nav.mobile.haml @@ -6,4 +6,4 @@ %li= link_to_unless_current t('account'), edit_user_path %li= link_to_unless_current t('privacy'), privacy_settings_path %li= link_to_unless_current t('_services'), services_path - %li= link_to_unless_current t('_applications'), user_applications_path + %li= link_to_unless_current t('_applications'), api_openid_connect_user_applications_path diff --git a/app/views/user_applications/_add_remove_applications.haml b/app/views/user_applications/_add_remove_applications.haml deleted file mode 100644 index ed50dba8c..000000000 --- a/app/views/user_applications/_add_remove_applications.haml +++ /dev/null @@ -1,28 +0,0 @@ -- if @user_apps.applications? - %ul.list-group - - @user_apps.user_applications.each do |app| - %li.list-group-item.authorized-application - .application-img - - if app[:image] - = image_tag app[:image], class: "img-responsive" - - else - %i.entypo-browser - .application-authorizations - - if app[:authorizations].count > 0 - %h4=t("user_applications.index.access", name: app[:name]) - %ul - - app[:authorizations].each do |authorization| - %li - %b= t("user_applications.scopes.#{authorization}.name") - %p= t("user_applications.scopes.#{authorization}.description") - - else - .well - =t("user_applications.show.no_requirement") - = form_for "application", url: "#{api_openid_connect_authorizations_path}/#{app[:id]}", - html: { method: :delete, class: "form-horizontal"} do |f| - .clearfix= f.submit t("user_applications.revoke_autorization"), class: "btn btn-danger pull-right app-revoke" - -- else - .well - %h4 - = t("user_applications.no_applications") diff --git a/app/views/user_applications/index.mobile.haml b/app/views/user_applications/index.mobile.haml deleted file mode 100644 index 27d0b6b82..000000000 --- a/app/views/user_applications/index.mobile.haml +++ /dev/null @@ -1,13 +0,0 @@ -.settings_container.applications-page - - content_for :page_title do - = t('.edit_applications') - - = render 'shared/settings_nav' - - .container-fluid - .row - .col-md-12.applications-explenation - = t('.applications_explanation') - .col-md-12 - = render 'add_remove_applications' - diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index b259e1939..8186abeac 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -886,11 +886,35 @@ en: authorizations: new: redirection_message: "Are you sure you want to give access to %{redirect_uri}?" - form: + access: "%{name} requires access to:" + no_requirement: "%{name} requires no permissions" approve: "Approve" deny: "Deny" destroy: fail: "The attempt to revoke the authorization with ID %{id} has failed" + user_applications: + index: + edit_applications: "Applications" + title: "Authorized applications" + access: "%{name} has access to:" + no_requirement: "%{name} requires no permissions" + applications_explanation: "Here is a list of applications to which you have authorized" + no_applications: "You have no authorized applications" + revoke_autorization: "Revoke" + scopes: + openid: + name: "basic profile" + description: "This allows the application to read your basic profile" + extended: + name: "extended profile" + description: "This allows the application to read your extended profile" + read: + name: "read profile, stream and conversations" + description: "This allows the application to read your stream, your conversations and your complete profile" + write: + name: "send posts, conversations and reactions" + description: "This allows the application to send new posts, write conversations, and send reactions" + people: zero: "No people" one: "1 person" @@ -1476,27 +1500,3 @@ en: disabled: "Not available" open: "Open" closed: "Closed" - - user_applications: - index: - edit_applications: "Applications" - title: "Authorized applications" - access: "%{name} is authorized access to:" - no_requirement: "This application requires no permissions" - applications_explanation: "Here is a list of applications to which you have authorized access" - no_applications: "You have no authorized applications" - revoke_autorization: "Revoke" - scopes: - openid: - name: "basic profile" - description: "This allows the application to read your basic profile" - extended: - name: "extended profile" - description: "This allows the application to read your extended profile" - read: - name: "read profile, stream and conversations" - description: "This allows the application to read your stream, your conversations and your complete profile" - write: - name: "send posts, conversations and reactions" - description: "This allows the application to send new posts, write conversations, and send reactions" - diff --git a/config/routes.rb b/config/routes.rb index 79e585e97..e4ae8ef8a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -248,14 +248,12 @@ Diaspora::Application.routes.draw do # See http://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation resources :authorizations, only: %i(new create destroy) post "authorizations/new", to: "authorizations#new" - + get "user_applications", to: "user_applications#index" get "jwks.json", to: "id_tokens#jwks" - get "user_info", to: "user_info#show" end end get ".well-known/webfinger", to: "api/openid_connect/discovery#webfinger" get ".well-known/openid-configuration", to: "api/openid_connect/discovery#configuration" - get "user_applications", to: "user_applications#index" end diff --git a/features/support/paths.rb b/features/support/paths.rb index 19ada8e40..368d25b7e 100644 --- a/features/support/paths.rb +++ b/features/support/paths.rb @@ -37,7 +37,7 @@ module NavigationHelpers when /^forgot password page$/ new_user_password_path when /^user applications page$/ - user_applications_path + api_openid_connect_user_applications_path when %r{^"(/.*)"} Regexp.last_match(1) else diff --git a/lib/api/openid_connect/authorization_point/endpoint.rb b/lib/api/openid_connect/authorization_point/endpoint.rb index bfc5d5ea4..c888e828c 100644 --- a/lib/api/openid_connect/authorization_point/endpoint.rb +++ b/lib/api/openid_connect/authorization_point/endpoint.rb @@ -10,7 +10,7 @@ module Api @user = user @app = Rack::OAuth2::Server::Authorize.new do |req, res| build_attributes(req, res) - if OAuthApplication.available_response_types.include? Array(req.response_type).map(&:to_s).join(" ") + if OAuthApplication.available_response_types.include? Array(req.response_type).join(" ") handle_response_type(req, res) else req.unsupported_response_type! @@ -46,11 +46,14 @@ module Api def build_scopes(req) @scopes = req.scope.map {|scope| scope.tap do |scope_name| - # TODO: Use enum - req.invalid_scope! "Unknown scope: #{scope_name}" unless %w(openid read write).include? scope_name + req.invalid_scope! "Unknown scope: #{scope_name}" unless scopes.include? scope_name end } end + + def scopes + Api::OpenidConnect::Authorization.scopes + end end end end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index aa4f13a98..7527bacce 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -302,7 +302,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do context "with non-existent authorization" do it "raises an error" do delete :destroy, id: 123_456_789 - expect(response).to redirect_to(user_applications_url) + expect(response).to redirect_to(api_openid_connect_user_applications_url) expect(flash[:error]).to eq("The attempt to revoke the authorization with ID 123456789 has failed") end end From 8c2af744470a6a0c0945be22eb8ad1b1bede65a2 Mon Sep 17 00:00:00 2001 From: augier Date: Fri, 28 Aug 2015 16:46:04 -0700 Subject: [PATCH 059/105] Fixing last remarks --- app/assets/stylesheets/user_applications.scss | 1 + .../openid_connect/discovery_controller.rb | 2 +- .../api/openid_connect/authorization.rb | 11 ++++------ .../api/openid_connect/authorization_scope.rb | 11 ---------- .../api/openid_connect/o_auth_application.rb | 4 ++++ app/models/api/openid_connect/scope.rb | 9 -------- .../authorizations/new.html.haml | 16 +++++++------- config/locales/diaspora/en.yml | 2 +- features/step_definitions/auth_code_steps.rb | 21 ++++++++++--------- features/support/paths.rb | 4 ++-- .../authorization_point/endpoint.rb | 8 +++---- 11 files changed, 36 insertions(+), 53 deletions(-) delete mode 100644 app/models/api/openid_connect/authorization_scope.rb delete mode 100644 app/models/api/openid_connect/scope.rb diff --git a/app/assets/stylesheets/user_applications.scss b/app/assets/stylesheets/user_applications.scss index e3cb69436..2b49bc5e4 100644 --- a/app/assets/stylesheets/user_applications.scss +++ b/app/assets/stylesheets/user_applications.scss @@ -26,3 +26,4 @@ } .user-consent { margin-top: 20px; } +.approval-button { display: inline; } diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index ca8dd18cf..8b4602338 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -20,7 +20,7 @@ module Api token_endpoint: api_openid_connect_access_tokens_url, userinfo_endpoint: api_openid_connect_user_info_url, jwks_uri: api_openid_connect_url, - scopes_supported: %w(openid read write), + scopes_supported: Api::OpenidConnect::Authorization::SCOPES, response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types, request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), subject_types_supported: %w(public pairwise), diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index 2cdfb1e6d..90ee38660 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -17,15 +17,16 @@ module Api scope :with_redirect_uri, ->(given_uri) { where redirect_uri: given_uri } + SCOPES = %w(openid read write) + def setup self.refresh_token = SecureRandom.hex(32) end - def validate_scope_names return unless scopes scopes.each do |scope| - errors.add(:scope, "is not a valid scope name") unless scopes.include? scope + errors.add(:scope, "is not a valid scope name") unless SCOPES.include? scope end end @@ -57,13 +58,9 @@ module Api def self.find_by_refresh_token(client_id, refresh_token) Api::OpenidConnect::Authorization.joins(:o_auth_application).find_by( - o_auth_applications: {client_id: client_id}, refresh_token: refresh_token) + o_auth_applications: {client_id: client_id}, refresh_token: refresh_token) end - def self.scopes - %w(openid read write) - end - def self.use_code(code) return unless code find_by(code: code).tap do |auth| diff --git a/app/models/api/openid_connect/authorization_scope.rb b/app/models/api/openid_connect/authorization_scope.rb deleted file mode 100644 index fce15b225..000000000 --- a/app/models/api/openid_connect/authorization_scope.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Api - module OpenidConnect - class AuthorizationScope < ActiveRecord::Base - belongs_to :authorization - belongs_to :scope - - validates :authorization, presence: true - validates :scope, presence: true - end - end -end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index a136fc123..8fc4c6dc3 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -7,12 +7,16 @@ module Api validates :client_id, presence: true, uniqueness: true validates :client_secret, presence: true validates :client_name, presence: true + validates_uniqueness_of :client_name, scope: :redirect_uris %i(redirect_uris response_types grant_types contacts).each do |serializable| serialize serializable, JSON end before_validation :setup, on: :create + before_validation do + redirect_uris.sort! + end def setup self.client_id = SecureRandom.hex(16) diff --git a/app/models/api/openid_connect/scope.rb b/app/models/api/openid_connect/scope.rb deleted file mode 100644 index aaf4794bf..000000000 --- a/app/models/api/openid_connect/scope.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Api - module OpenidConnect - class Scope < ActiveRecord::Base - has_many :authorizations, through: :authorization_scopes - - validates :name, presence: true, uniqueness: true - end - end -end diff --git a/app/views/api/openid_connect/authorizations/new.html.haml b/app/views/api/openid_connect/authorizations/new.html.haml index 4443cba3b..e292f3125 100644 --- a/app/views/api/openid_connect/authorizations/new.html.haml +++ b/app/views/api/openid_connect/authorizations/new.html.haml @@ -3,11 +3,11 @@ %li.list-group-item.authorized-application = render "grants_list", app: @app - .clearfix - = form_tag api_openid_connect_authorizations_path, class: "pull-right" do - %span - = submit_tag t(".deny"), class: "btn btn-danger" - = hidden_field_tag :deny, false - %span - = submit_tag t(".approve"), class: "btn btn-primary" - = hidden_field_tag :approve, true + .clearfix.pull-right + = form_tag api_openid_connect_authorizations_path, class: "approval-button" do + = submit_tag t(".deny"), class: "btn btn-danger" + = hidden_field_tag :approve, false + + = form_tag api_openid_connect_authorizations_path, class: "approval-button"do + = submit_tag t(".approve"), class: "btn btn-primary" + = hidden_field_tag :approve, true diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 8186abeac..89095b5cf 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -898,7 +898,7 @@ en: title: "Authorized applications" access: "%{name} has access to:" no_requirement: "%{name} requires no permissions" - applications_explanation: "Here is a list of applications to which you have authorized" + applications_explanation: "Here is a list of applications you have authorized" no_applications: "You have no authorized applications" revoke_autorization: "Revoke" scopes: diff --git a/features/step_definitions/auth_code_steps.rb b/features/step_definitions/auth_code_steps.rb index 37b4f7b8b..3d0d11927 100644 --- a/features/step_definitions/auth_code_steps.rb +++ b/features/step_definitions/auth_code_steps.rb @@ -1,21 +1,22 @@ -o_auth_query_params = %i( - redirect_uri=http://localhost:3000 - response_type=code - scope=openid%20read - nonce=hello - state=hi -).join("&") +O_AUTH_QUERY_PARAMS = { + redirect_uri: "http://localhost:3000", + response_type: "code", + scope: "openid read", + nonce: "hello", + state: "hi" +} Given /^I send a post request from that client to the code flow authorization endpoint$/ do client_json = JSON.parse(last_response.body) @client_id = client_json["client_id"] @client_secret = client_json["client_secret"] - visit new_api_openid_connect_authorization_path + - "?client_id=#{@client_id}&#{o_auth_query_params}" + params = O_AUTH_QUERY_PARAMS.merge(client_id: @client_id) + visit new_api_openid_connect_authorization_path(params) end Given /^I send a post request from that client to the code flow authorization endpoint using a invalid client id/ do - visit new_api_openid_connect_authorization_path + "?client_id=randomid&#{o_auth_query_params}" + params = O_AUTH_QUERY_PARAMS.merge(client_id: "randomid") + visit new_api_openid_connect_authorization_path(params) end When /^I parse the auth code and create a request to the token endpoint$/ do diff --git a/features/support/paths.rb b/features/support/paths.rb index 368d25b7e..223915aed 100644 --- a/features/support/paths.rb +++ b/features/support/paths.rb @@ -7,6 +7,8 @@ module NavigationHelpers stream_path when /^the mobile path$/ force_mobile_path + when /^the user applications page$/ + api_openid_connect_user_applications_path when /^the tag page for "([^\"]*)"$/ tag_path(Regexp.last_match(1)) when /^its ([\w ]+) page$/ @@ -36,8 +38,6 @@ module NavigationHelpers edit_user_path when /^forgot password page$/ new_user_password_path - when /^user applications page$/ - api_openid_connect_user_applications_path when %r{^"(/.*)"} Regexp.last_match(1) else diff --git a/lib/api/openid_connect/authorization_point/endpoint.rb b/lib/api/openid_connect/authorization_point/endpoint.rb index c888e828c..680d494f8 100644 --- a/lib/api/openid_connect/authorization_point/endpoint.rb +++ b/lib/api/openid_connect/authorization_point/endpoint.rb @@ -29,6 +29,10 @@ module Api raise NotImplementedError # Implemented by subclass end + def scopes + Api::OpenidConnect::Authorization::SCOPES + end + private def build_client(req) @@ -50,10 +54,6 @@ module Api end } end - - def scopes - Api::OpenidConnect::Authorization.scopes - end end end end From 9439a16d98d61dbe58de6e2a75513d7e623cebc2 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 28 Aug 2015 15:51:10 -0700 Subject: [PATCH 060/105] Fix failing auth code test and styles --- app/models/api/openid_connect/o_auth_application.rb | 3 +-- .../openid_connect/authorizations/_grants_list.haml | 4 ++-- .../user_applications/_add_remove_applications.haml | 2 +- .../user_applications/_grants_list.haml | 4 ++-- app/views/shared/_settings_nav.mobile.haml | 12 ++++++------ features/step_definitions/auth_code_steps.rb | 6 +++--- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 8fc4c6dc3..6ade4b9a8 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -6,8 +6,7 @@ module Api validates :client_id, presence: true, uniqueness: true validates :client_secret, presence: true - validates :client_name, presence: true - validates_uniqueness_of :client_name, scope: :redirect_uris + validates :client_name, presence: true, uniqueness: {scope: :redirect_uris} %i(redirect_uris response_types grant_types contacts).each do |serializable| serialize serializable, JSON diff --git a/app/views/api/openid_connect/authorizations/_grants_list.haml b/app/views/api/openid_connect/authorizations/_grants_list.haml index 022304303..72c183221 100644 --- a/app/views/api/openid_connect/authorizations/_grants_list.haml +++ b/app/views/api/openid_connect/authorizations/_grants_list.haml @@ -5,7 +5,7 @@ %i.entypo-browser .application-authorizations - if app[:authorizations].count > 0 - %h4=t("api.openid_connect.authorizations.new.access", name: app[:name]) + %h4= t("api.openid_connect.authorizations.new.access", name: app[:name]) %ul - app[:authorizations].each do |authorization| %li @@ -13,4 +13,4 @@ %p= t("api.openid_connect.scopes.#{authorization}.description") - else .well - =t("api.openid_connect.authorizations.new.no_requirement", name: app[:name]) + = t("api.openid_connect.authorizations.new.no_requirement", name: app[:name]) diff --git a/app/views/api/openid_connect/user_applications/_add_remove_applications.haml b/app/views/api/openid_connect/user_applications/_add_remove_applications.haml index 28afb9021..4837111f6 100644 --- a/app/views/api/openid_connect/user_applications/_add_remove_applications.haml +++ b/app/views/api/openid_connect/user_applications/_add_remove_applications.haml @@ -4,7 +4,7 @@ %li.list-group-item.authorized-application = render "grants_list", app: app = form_for "application", url: "#{api_openid_connect_authorizations_path}/#{app[:id]}", - html: { method: :delete, class: "form-horizontal"} do |f| + html: {method: :delete, class: "form-horizontal"} do |f| .clearfix= f.submit t("api.openid_connect.user_applications.revoke_autorization"), class: "btn btn-danger pull-right app-revoke" diff --git a/app/views/api/openid_connect/user_applications/_grants_list.haml b/app/views/api/openid_connect/user_applications/_grants_list.haml index e2c67b8ff..4b11c0030 100644 --- a/app/views/api/openid_connect/user_applications/_grants_list.haml +++ b/app/views/api/openid_connect/user_applications/_grants_list.haml @@ -5,7 +5,7 @@ %i.entypo-browser .application-authorizations - if app[:authorizations].count > 0 - %h4=t("api.openid_connect.user_applications.index.access", name: app[:name]) + %h4= t("api.openid_connect.user_applications.index.access", name: app[:name]) %ul - app[:authorizations].each do |authorization| %li @@ -13,4 +13,4 @@ %p= t("api.openid_connect.scopes.#{authorization}.description") - else .well - =t("api.openid_connect.user_applications.index.no_requirement",name: app[:name]) + = t("api.openid_connect.user_applications.index.no_requirement", name: app[:name]) diff --git a/app/views/shared/_settings_nav.mobile.haml b/app/views/shared/_settings_nav.mobile.haml index a85537dcb..aaeb0b319 100644 --- a/app/views/shared/_settings_nav.mobile.haml +++ b/app/views/shared/_settings_nav.mobile.haml @@ -1,9 +1,9 @@ #settings_nav - %h2= t('settings') + %h2= t("settings") %nav %ul - %li= link_to_unless_current t('profile'), edit_profile_path - %li= link_to_unless_current t('account'), edit_user_path - %li= link_to_unless_current t('privacy'), privacy_settings_path - %li= link_to_unless_current t('_services'), services_path - %li= link_to_unless_current t('_applications'), api_openid_connect_user_applications_path + %li= link_to_unless_current t("profile"), edit_profile_path + %li= link_to_unless_current t("account"), edit_user_path + %li= link_to_unless_current t("privacy"), privacy_settings_path + %li= link_to_unless_current t("_services"), services_path + %li= link_to_unless_current t("_applications"), api_openid_connect_user_applications_path diff --git a/features/step_definitions/auth_code_steps.rb b/features/step_definitions/auth_code_steps.rb index 3d0d11927..bec5caa34 100644 --- a/features/step_definitions/auth_code_steps.rb +++ b/features/step_definitions/auth_code_steps.rb @@ -1,4 +1,4 @@ -O_AUTH_QUERY_PARAMS = { +O_AUTH_QUERY_PARAMS_WITH_CODE = { redirect_uri: "http://localhost:3000", response_type: "code", scope: "openid read", @@ -10,12 +10,12 @@ Given /^I send a post request from that client to the code flow authorization en client_json = JSON.parse(last_response.body) @client_id = client_json["client_id"] @client_secret = client_json["client_secret"] - params = O_AUTH_QUERY_PARAMS.merge(client_id: @client_id) + params = O_AUTH_QUERY_PARAMS_WITH_CODE.merge(client_id: @client_id) visit new_api_openid_connect_authorization_path(params) end Given /^I send a post request from that client to the code flow authorization endpoint using a invalid client id/ do - params = O_AUTH_QUERY_PARAMS.merge(client_id: "randomid") + params = O_AUTH_QUERY_PARAMS_WITH_CODE.merge(client_id: "randomid") visit new_api_openid_connect_authorization_path(params) end From 4be9f4d5584595474d553a7c6cf712c088989082 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sat, 29 Aug 2015 09:19:07 -0700 Subject: [PATCH 061/105] Make client name optional --- app/models/api/openid_connect/o_auth_application.rb | 2 +- .../api/openid_connect/clients_controller_spec.rb | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 6ade4b9a8..fc8482b53 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -6,7 +6,7 @@ module Api validates :client_id, presence: true, uniqueness: true validates :client_secret, presence: true - validates :client_name, presence: true, uniqueness: {scope: :redirect_uris} + validates :client_name, uniqueness: {scope: :redirect_uris} %i(redirect_uris response_types grant_types contacts).each do |serializable| serialize serializable, JSON diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index 159a0059f..618909d69 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -24,17 +24,6 @@ describe Api::OpenidConnect::ClientsController, type: :controller do expect(client_json["error"]).to have_content("invalid_client_metadata") end end - - context "when redirect client_name is missing" do - it "should return a invalid_client_metadata error" do - post :create, redirect_uris: ["http://localhost"], response_types: [], grant_types: [], - application_type: "web", contacts: [], logo_uri: "http://example.com/logo.png", - client_uri: "http://example.com/client", policy_uri: "http://example.com/policy", - tos_uri: "http://example.com/tos" - client_json = JSON.parse(response.body) - expect(client_json["error"]).to have_content("invalid_client_metadata") - end - end end describe "#find" do From 21175e7eee1ded5ba37d5cfcd89765c3d452b6ff Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 25 Sep 2015 13:10:46 -0700 Subject: [PATCH 062/105] Allow POST requests for user info endpoint --- config/initializers/cors.rb | 2 +- config/routes.rb | 2 +- db/schema.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index e2afeff3e..6ca623de5 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -5,7 +5,7 @@ Rails.application.config.middleware.insert 0, Rack::Cors do resource "/webfinger" resource "/.well-known/webfinger" resource "/.well-known/openid-configuration" - resource "/api/openid_connect/user_info", methods: :get + resource "/api/openid_connect/user_info", methods: %i(get post) resource "/api/v0/*", methods: %i(get post delete) end end diff --git a/config/routes.rb b/config/routes.rb index e4ae8ef8a..beed890c6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -250,7 +250,7 @@ Diaspora::Application.routes.draw do post "authorizations/new", to: "authorizations#new" get "user_applications", to: "user_applications#index" get "jwks.json", to: "id_tokens#jwks" - get "user_info", to: "user_info#show" + match "user_info", to: "user_info#show", via: %i(get post) end end diff --git a/db/schema.rb b/db/schema.rb index 084e882a6..adff167f8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150801074555) do +ActiveRecord::Schema.define(version: 20150828132451) do create_table "account_deletions", force: :cascade do |t| t.string "diaspora_handle", limit: 255 From 4e18f3849dcc5d36bda93e68b597b869735d5517 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 13 Sep 2015 15:03:55 -0700 Subject: [PATCH 063/105] Remove GUID as primary key in ppid table --- ... 20150731123113_create_pairwise_pseudonymous_identifiers.rb} | 2 +- db/schema.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) rename db/migrate/{20150801074555_create_pairwise_pseudonymous_identifiers.rb => 20150731123113_create_pairwise_pseudonymous_identifiers.rb} (88%) diff --git a/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb b/db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb similarity index 88% rename from db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb rename to db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb index 3b346dda0..0cceecafd 100644 --- a/db/migrate/20150801074555_create_pairwise_pseudonymous_identifiers.rb +++ b/db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb @@ -4,7 +4,7 @@ class CreatePairwisePseudonymousIdentifiers < ActiveRecord::Migration t.belongs_to :o_auth_application, index: true t.belongs_to :user, index: true - t.primary_key :guid, :string, limit: 32 + t.string :guid, :string, limit: 32 t.string :sector_identifier end add_foreign_key :ppid, :o_auth_applications diff --git a/db/schema.rb b/db/schema.rb index adff167f8..51bfa6262 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -465,6 +465,7 @@ ActiveRecord::Schema.define(version: 20150828132451) do t.integer "o_auth_application_id", limit: 4 t.integer "user_id", limit: 4 t.string "guid", limit: 32 + t.string "string", limit: 32 t.string "sector_identifier", limit: 255 end From a76f51a6a5286457512a6c02fe2ecddb9954c79f Mon Sep 17 00:00:00 2001 From: theworldbright Date: Thu, 24 Sep 2015 21:46:16 -0700 Subject: [PATCH 064/105] Use redirect_uri if no sector identifier for ppid As according to http://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg: "If the Client has not provided a value for sector_identifier_uri in Dynamic Client Registration [OpenID.Registration], the Sector Identifier used for pairwise identifier calculation is the host component of the registered redirect_uri." --- .../api/openid_connect/user_info_controller.rb | 2 +- app/models/api/openid_connect/id_token.rb | 11 +++-------- .../pairwise_pseudonymous_identifier.rb | 2 +- app/serializers/user_info_serializer.rb | 9 +-------- ..._create_pairwise_pseudonymous_identifiers.rb | 2 +- db/schema.rb | 2 +- .../subject_identifier_creator.rb | 17 +++++++++++++++++ .../api/user_info_controller_spec.rb | 2 +- .../api/openid_connect/token_endpoint_spec.rb | 2 +- 9 files changed, 27 insertions(+), 22 deletions(-) create mode 100644 lib/api/openid_connect/subject_identifier_creator.rb diff --git a/app/controllers/api/openid_connect/user_info_controller.rb b/app/controllers/api/openid_connect/user_info_controller.rb index 76b063ba5..d5e7a8cf0 100644 --- a/app/controllers/api/openid_connect/user_info_controller.rb +++ b/app/controllers/api/openid_connect/user_info_controller.rb @@ -4,7 +4,7 @@ module Api include Api::OpenidConnect::ProtectedResourceEndpoint before_action do - require_access_token ["openid"] + require_access_token %w(openid) end def show diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb index 58f8dd453..53bcf3432 100644 --- a/app/models/api/openid_connect/id_token.rb +++ b/app/models/api/openid_connect/id_token.rb @@ -1,3 +1,5 @@ +require "uri" + module Api module OpenidConnect class IdToken < ActiveRecord::Base @@ -36,14 +38,7 @@ module Api end def build_sub - if authorization.o_auth_application.ppid? - sector_identifier = authorization.o_auth_application.sector_identifier_uri - pairwise_pseudonymous_identifier = - authorization.user.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: sector_identifier) - pairwise_pseudonymous_identifier.guid - else - authorization.user.diaspora_handle - end + Api::OpenidConnect::SubjectIdentifierCreator.createSub(authorization) end end end diff --git a/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb b/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb index 06c115daf..4821ea1a2 100644 --- a/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb +++ b/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb @@ -7,7 +7,7 @@ module Api belongs_to :user validates :user, presence: true - validates :sector_identifier, presence: true, uniqueness: {scope: :user} + validates :identifier, presence: true, uniqueness: {scope: :user} validates :guid, presence: true, uniqueness: true before_validation :setup, on: :create diff --git a/app/serializers/user_info_serializer.rb b/app/serializers/user_info_serializer.rb index 86c89ef47..4fae962a3 100644 --- a/app/serializers/user_info_serializer.rb +++ b/app/serializers/user_info_serializer.rb @@ -3,14 +3,7 @@ class UserInfoSerializer < ActiveModel::Serializer def sub auth = serialization_options[:authorization] - if auth.o_auth_application.ppid? - sector_identifier = auth.o_auth_application.sector_identifier_uri - pairwise_pseudonymous_identifier = - object.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: sector_identifier) - pairwise_pseudonymous_identifier.guid - else - object.diaspora_handle - end + Api::OpenidConnect::SubjectIdentifierCreator.createSub(auth) end def nickname diff --git a/db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb b/db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb index 0cceecafd..40fc03d09 100644 --- a/db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb +++ b/db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb @@ -5,7 +5,7 @@ class CreatePairwisePseudonymousIdentifiers < ActiveRecord::Migration t.belongs_to :user, index: true t.string :guid, :string, limit: 32 - t.string :sector_identifier + t.string :identifier end add_foreign_key :ppid, :o_auth_applications add_foreign_key :ppid, :users diff --git a/db/schema.rb b/db/schema.rb index 51bfa6262..52b823bdd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -466,7 +466,7 @@ ActiveRecord::Schema.define(version: 20150828132451) do t.integer "user_id", limit: 4 t.string "guid", limit: 32 t.string "string", limit: 32 - t.string "sector_identifier", limit: 255 + t.string "identifier", limit: 255 end add_index "ppid", ["o_auth_application_id"], name: "index_ppid_on_o_auth_application_id", using: :btree diff --git a/lib/api/openid_connect/subject_identifier_creator.rb b/lib/api/openid_connect/subject_identifier_creator.rb new file mode 100644 index 000000000..3c39e93a1 --- /dev/null +++ b/lib/api/openid_connect/subject_identifier_creator.rb @@ -0,0 +1,17 @@ +module Api + module OpenidConnect + class SubjectIdentifierCreator + def self.createSub(auth) + if auth.o_auth_application.ppid? + identifier = auth.o_auth_application.sector_identifier_uri || + URI.parse(auth.o_auth_application.redirect_uris[0]).host + pairwise_pseudonymous_identifier = + auth.user.pairwise_pseudonymous_identifiers.find_or_create_by(identifier: identifier) + pairwise_pseudonymous_identifier.guid + else + auth.user.diaspora_handle + end + end + end + end +end diff --git a/spec/integration/api/user_info_controller_spec.rb b/spec/integration/api/user_info_controller_spec.rb index dd4fb95fe..1f237a172 100644 --- a/spec/integration/api/user_info_controller_spec.rb +++ b/spec/integration/api/user_info_controller_spec.rb @@ -12,7 +12,7 @@ describe Api::OpenidConnect::UserInfoController do it "shows the info" do json_body = JSON.parse(response.body) expected_sub = - @user.pairwise_pseudonymous_identifiers.find_or_create_by(sector_identifier: "https://example.com/uri").guid + @user.pairwise_pseudonymous_identifiers.find_or_create_by(identifier: "https://example.com/uri").guid expect(json_body["sub"]).to eq(expected_sub) expect(json_body["nickname"]).to eq(@user.name) expect(json_body["profile"]).to eq(File.join(AppConfig.environment.url, "people", @user.guid).to_s) diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index a3e0e20c6..66554b81e 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -21,7 +21,7 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do encoded_id_token = json["id_token"] decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY - expected_guid = bob.pairwise_pseudonymous_identifiers.find_by(sector_identifier: "https://example.com/uri").guid + expected_guid = bob.pairwise_pseudonymous_identifiers.find_by(identifier: "https://example.com/uri").guid expect(decoded_token.sub).to eq(expected_guid) expect(decoded_token.exp).to be > Time.zone.now.utc.to_i end From 5f19d8ffe688662f49b254796ed5bebe6cb9cc6c Mon Sep 17 00:00:00 2001 From: theworldbright Date: Thu, 24 Sep 2015 21:50:17 -0700 Subject: [PATCH 065/105] Add acr value --- app/models/api/openid_connect/id_token.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb index 53bcf3432..b6d330376 100644 --- a/app/models/api/openid_connect/id_token.rb +++ b/app/models/api/openid_connect/id_token.rb @@ -33,7 +33,8 @@ module Api exp: expires_at.to_i, iat: created_at.to_i, auth_time: authorization.user.current_sign_in_at.to_i, - nonce: nonce + nonce: nonce, + acr: 0 } end From 1dcefdb9986882f8082282f46370a99a082ced07 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 25 Sep 2015 13:13:36 -0700 Subject: [PATCH 066/105] Validate sector identifier uri and redirect uri --- .../api/openid_connect/clients_controller.rb | 23 +++++++++-------- .../api/openid_connect/o_auth_application.rb | 25 ++++++++++++++++++- .../exception/invalid_redirect_uri.rb | 11 ++++++++ .../invalid_sector_identifier_uri.rb | 11 ++++++++ .../openid_connect/clients_controller_spec.rb | 4 +++ 5 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 lib/api/openid_connect/exception/invalid_redirect_uri.rb create mode 100644 lib/api/openid_connect/exception/invalid_sector_identifier_uri.rb diff --git a/app/controllers/api/openid_connect/clients_controller.rb b/app/controllers/api/openid_connect/clients_controller.rb index 28d34bae8..ae906c58a 100644 --- a/app/controllers/api/openid_connect/clients_controller.rb +++ b/app/controllers/api/openid_connect/clients_controller.rb @@ -5,10 +5,15 @@ module Api http_error_page_as_json(e) end - rescue_from OpenIDConnect::ValidationFailed, ActiveRecord::RecordInvalid do |e| + rescue_from OpenIDConnect::ValidationFailed, + ActiveRecord::RecordInvalid, Api::OpenidConnect::Exception::InvalidSectorIdentifierUri do |e| validation_fail_as_json(e) end + rescue_from Api::OpenidConnect::Exception::InvalidRedirectUri do |e| + validation_fail_redirect_uri(e) + end + def create registrar = OpenIDConnect::Client::Registrar.new(request.url, params) client = Api::OpenidConnect::OAuthApplication.register! registrar @@ -27,19 +32,15 @@ module Api private def http_error_page_as_json(e) - render json: - { - error: :invalid_request, - error_description: e.message - }, status: 400 + render json: { error: :invalid_request, error_description: e.message}, status: 400 end def validation_fail_as_json(e) - render json: - { - error: :invalid_client_metadata, - error_description: e.message - }, status: 400 + render json: {error: :invalid_client_metadata, error_description: e.message}, status: 400 + end + + def validation_fail_redirect_uri(e) + render json: {error: :invalid_redirect_uri, error_description: e.message}, status: 400 end end end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index fc8482b53..668592dab 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -39,7 +39,30 @@ module Api private def build_client_application(registrar) - create! registrar_attributes(registrar) + attributes = registrar_attributes(registrar) + check_sector_identifier_uri(attributes) + check_redirect_uris(attributes) + create! attributes + end + + def check_sector_identifier_uri(attributes) + sector_identifier_uri = attributes[:sector_identifier_uri] + return unless sector_identifier_uri + uri = URI.parse(sector_identifier_uri) + response = Net::HTTP.get_response(uri) + sector_identifier_uri_json = JSON.parse(response.body) + redirect_uris = attributes[:redirect_uris] + sector_identifier_uri_includes_redirect_uris = (redirect_uris - sector_identifier_uri_json).empty? + return if sector_identifier_uri_includes_redirect_uris + raise Api::OpenidConnect::Exception::InvalidSectorIdentifierUri.new + end + + def check_redirect_uris(attributes) + redirect_uris = attributes[:redirect_uris] + uri_array = redirect_uris.map {|uri| URI(uri) } + any_uri_contains_fragment = uri_array.any? {|uri| !uri.fragment.nil? } + return unless any_uri_contains_fragment + raise Api::OpenidConnect::Exception::InvalidRedirectUri.new end def supported_metadata diff --git a/lib/api/openid_connect/exception/invalid_redirect_uri.rb b/lib/api/openid_connect/exception/invalid_redirect_uri.rb new file mode 100644 index 000000000..a407505b4 --- /dev/null +++ b/lib/api/openid_connect/exception/invalid_redirect_uri.rb @@ -0,0 +1,11 @@ +module Api + module OpenidConnect + module Exception + class InvalidRedirectUri < ::ArgumentError + def initialize + super "Redirect uri contains fragment" + end + end + end + end +end diff --git a/lib/api/openid_connect/exception/invalid_sector_identifier_uri.rb b/lib/api/openid_connect/exception/invalid_sector_identifier_uri.rb new file mode 100644 index 000000000..6383aee22 --- /dev/null +++ b/lib/api/openid_connect/exception/invalid_sector_identifier_uri.rb @@ -0,0 +1,11 @@ +module Api + module OpenidConnect + module Exception + class InvalidSectorIdentifierUri < ::ArgumentError + def initialize + super "Invalid sector identifier uri" + end + end + end + end +end diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index 618909d69..1b138386a 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -4,6 +4,10 @@ describe Api::OpenidConnect::ClientsController, type: :controller do describe "#create" do context "when valid parameters are passed" do it "should return a client id" do + stub_request(:get, "http://example.com/uris") + .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "Host" => "example.com", "User-Agent" => "Ruby"}) + .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client", From fd467cd42bf08b89153076532e78c812f8a146ef Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sat, 3 Oct 2015 17:47:23 -0700 Subject: [PATCH 067/105] Add private_key_jwt support See - http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication - https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata --- .gitignore | 1 + .../api/openid_connect/clients_controller.rb | 2 +- .../openid_connect/discovery_controller.rb | 2 +- .../token_endpoint_controller.rb | 64 +++++++++++ .../api/openid_connect/o_auth_application.rb | 23 +++- config/routes.rb | 2 +- ...150613202109_create_o_auth_applications.rb | 4 + db/schema.rb | 37 ++++--- .../openid_connect/clients_controller_spec.rb | 81 ++++++++++++++ spec/factories.rb | 7 ++ spec/fixtures/jwks.json | 1 + .../api/openid_connect/token_endpoint_spec.rb | 100 ++++++++++++++++++ spec/spec_helper.rb | 4 + 13 files changed, 307 insertions(+), 21 deletions(-) create mode 100644 app/controllers/api/openid_connect/token_endpoint_controller.rb create mode 100644 spec/fixtures/jwks.json diff --git a/.gitignore b/.gitignore index 3fc25bd96..3f1d9c02a 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ config/database.yml .rvmrc_custom .rvmrc.local config/oidc_key.pem +config/jwks/ # Mailing list stuff config/email_offset diff --git a/app/controllers/api/openid_connect/clients_controller.rb b/app/controllers/api/openid_connect/clients_controller.rb index ae906c58a..d1f56dabe 100644 --- a/app/controllers/api/openid_connect/clients_controller.rb +++ b/app/controllers/api/openid_connect/clients_controller.rb @@ -32,7 +32,7 @@ module Api private def http_error_page_as_json(e) - render json: { error: :invalid_request, error_description: e.message}, status: 400 + render json: {error: :invalid_request, error_description: e.message}, status: 400 end def validation_fail_as_json(e) diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index 8b4602338..5a12c6007 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -25,7 +25,7 @@ module Api request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), subject_types_supported: %w(public pairwise), id_token_signing_alg_values_supported: %i(RS256), - token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post), + token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post private_key_jwt), claims_supported: %w(sub nickname profile picture) ) end diff --git a/app/controllers/api/openid_connect/token_endpoint_controller.rb b/app/controllers/api/openid_connect/token_endpoint_controller.rb new file mode 100644 index 000000000..86d3d2f79 --- /dev/null +++ b/app/controllers/api/openid_connect/token_endpoint_controller.rb @@ -0,0 +1,64 @@ +module Api + module OpenidConnect + class TokenEndpointController < ApplicationController + def create + req = Rack::Request.new(request.env) + if req["client_assertion_type"] == "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + handle_jwt_bearer(req) + end + self.status, self.response.headers, self.response_body = Api::OpenidConnect::TokenEndpoint.new.call(request.env) + nil + end + + private + + def handle_jwt_bearer(req) + jwt_string = req["client_assertion"] + jwt = JSON::JWT.decode jwt_string, :skip_verification + o_auth_app = Api::OpenidConnect::OAuthApplication.find_by(client_id: jwt["iss"]) + raise Rack::OAuth2::Server::Authorize::BadRequest(:invalid_request) unless o_auth_app + public_key = fetch_public_key(o_auth_app, jwt) + JSON::JWT.decode(jwt_string, JSON::JWK.new(public_key).to_key) + req.update_param("client_id", o_auth_app.client_id) + req.update_param("client_secret", o_auth_app.client_secret) + end + + def fetch_public_key(o_auth_app, jwt) + jwks_file_path = File.join(Rails.root, "config", "jwks", o_auth_app.jwks_file) + public_key = fetch_public_key_from_json(File.read(jwks_file_path), jwt) + if public_key.empty? && o_auth_app.jwks_uri + uri = URI.parse(o_auth_app.jwks_uri) + response = Net::HTTP.get_response(uri) + File.write jwks_file_path, response.body + public_key = fetch_public_key_from_json(response.body, jwt) + end + raise Rack::OAuth2::Server::Authorize::BadRequest(:unauthorized_client) if public_key.empty? + public_key + end + + def fetch_public_key_from_json(string, jwt) + json = JSON.parse(string) + keys = json["keys"] + public_key = get_key_from_kid(keys, jwt.header["kid"]) + public_key + end + + def get_key_from_kid(keys, kid) + keys.each do |key| + return key if key.has_value?(kid) + end + end + + rescue_from Rack::OAuth2::Server::Authorize::BadRequest, JSON::JWT::InvalidFormat do |e| + logger.info e.backtrace[0, 10].join("\n") + render json: {error: :invalid_request, error_description: e.message, status: e.status} + end + rescue_from JSON::JWT::InvalidFormat do |e| + render json: {error: :invalid_request, error_description: e.message, status: 400} + end + rescue_from JSON::JWT::VerificationFailed do |e| + render json: {error: :invalid_grant, error_description: e.message, status: 400} + end + end + end +end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 668592dab..a89fa0c05 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -1,3 +1,5 @@ +require "digest" + module Api module OpenidConnect class OAuthApplication < ActiveRecord::Base @@ -68,7 +70,7 @@ module Api def supported_metadata %i(client_name response_types grant_types application_type contacts logo_uri client_uri policy_uri tos_uri redirect_uris - sector_identifier_uri subject_type) + sector_identifier_uri subject_type token_endpoint_auth_method jwks jwks_uri) end def registrar_attributes(registrar) @@ -77,11 +79,30 @@ module Api next unless value if key == :subject_type attr[:ppid] = (value == "pairwise") + elsif key == :jwks_uri + uri = URI.parse(value) + response = Net::HTTP.get_response(uri) + file_name = create_file_path(response.body) + attr[:jwks_file] = file_name + ".json" + attr[:jwks_uri] = value + elsif key == :jwks + file_name = create_file_path(value.to_json) + attr[:jwks_file] = file_name + ".json" else attr[key] = value end end end + + def create_file_path(content) + file_name = Base64.urlsafe_encode64(Digest::SHA256.base64digest(content)) + directory_name = File.join(Rails.root, "config", "jwks") + Dir.mkdir(directory_name) unless File.exist?(directory_name) + jwk_file_path = File.join(Rails.root, "config", "jwks", file_name + ".json") + File.write jwk_file_path, content + File.chmod(0600, jwk_file_path) + file_name + end end end end diff --git a/config/routes.rb b/config/routes.rb index beed890c6..218c48911 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -242,7 +242,7 @@ Diaspora::Application.routes.draw do resources :clients, only: :create get "clients/find", to: "clients#find" - post "access_tokens", to: proc {|env| Api::OpenidConnect::TokenEndpoint.new.call(env) } + post "access_tokens", to: "token_endpoint#create" # Authorization Servers MUST support the use of the HTTP GET and POST methods at the Authorization Endpoint # See http://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index 1e7cf290f..4b4db75f2 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -16,6 +16,10 @@ class CreateOAuthApplications < ActiveRecord::Migration t.string :policy_uri t.string :tos_uri t.string :sector_identifier_uri + t.string :token_endpoint_auth_method + t.string :jwks_uri + t.string :jwks_file + t.boolean :ppid, default: false t.timestamps null: false diff --git a/db/schema.rb b/db/schema.rb index 52b823bdd..8fce36389 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -273,23 +273,26 @@ ActiveRecord::Schema.define(version: 20150828132451) do add_index "o_auth_access_tokens", ["token"], name: "index_o_auth_access_tokens_on_token", unique: true, length: {"token"=>191}, using: :btree create_table "o_auth_applications", force: :cascade do |t| - t.integer "user_id", limit: 4 - t.string "client_id", limit: 255 - t.string "client_secret", limit: 255 - t.string "client_name", limit: 255 - t.string "redirect_uris", limit: 255 - t.string "response_types", limit: 255 - t.string "grant_types", limit: 255 - t.string "application_type", limit: 255, default: "web" - t.string "contacts", limit: 255 - t.string "logo_uri", limit: 255 - t.string "client_uri", limit: 255 - t.string "policy_uri", limit: 255 - t.string "tos_uri", limit: 255 - t.string "sector_identifier_uri", limit: 255 - t.boolean "ppid", default: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "user_id", limit: 4 + t.string "client_id", limit: 255 + t.string "client_secret", limit: 255 + t.string "client_name", limit: 255 + t.string "redirect_uris", limit: 255 + t.string "response_types", limit: 255 + t.string "grant_types", limit: 255 + t.string "application_type", limit: 255, default: "web" + t.string "contacts", limit: 255 + t.string "logo_uri", limit: 255 + t.string "client_uri", limit: 255 + t.string "policy_uri", limit: 255 + t.string "tos_uri", limit: 255 + t.string "sector_identifier_uri", limit: 255 + t.string "token_endpoint_auth_method", limit: 255 + t.string "jwks_uri", limit: 255 + t.string "jwks_file", limit: 255 + t.boolean "ppid", default: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "o_auth_applications", ["client_id"], name: "index_o_auth_applications_on_client_id", unique: true, length: {"client_id"=>191}, using: :btree diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index 1b138386a..d124ff042 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -19,6 +19,87 @@ describe Api::OpenidConnect::ClientsController, type: :controller do end end + context "when valid parameters with jwks is passed" do + it "should return a client id" do + stub_request(:get, "http://example.com/uris") + .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "Host" => "example.com", "User-Agent" => "Ruby"}) + .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) + post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", + response_types: [], grant_types: [], application_type: "web", contacts: [], + logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client", + policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos", + sector_identifier_uri: "http://example.com/uris", subject_type: "pairwise", + token_endpoint_auth_method: "private_key_jwt", + "jwks": { + "keys": + [ + { + "use": "enc", + "e": "AQAB", + "d": "-lTBWkI-----lvCO6tuiDsR4qgJnUwnndQFwEI_4mLmD3iNWXrc8N--5Cjq55eLtuJjtvuQ", + "n": "--zYRQNDvIVsBDLQQIgrbctuGqj6lrXb31Jj3JIEYqH_4h5X9d0Q", + "q": "1q-r----pFtyTz_JksYYaotc_Z3Zy-Szw6a39IDbuYGy1qL-15oQuc", + "p": "-BfRjdgYouy4c6xAnGDgSMTip1YnPRyvbMaoYT9E_tEcBW5wOeoc", + "kid": "a0", + "kty": "RSA" + }, + {"use": "sig", + "e": "AQAB", + "d": "--x-gW---LRPowKrdvTuTo2p--HMI0pIEeFs7H_u5OW3jihjvoFClGPynHQhgWmQzlQRvWRXh6FhDVqFeGQ", + "n": "---TyeadDqQPWgbqX69UzcGq5irhzN8cpZ_JaTk3Y_uV6owanTZLVvCgdjaAnMYeZhb0KFw", + "q": "5E5XKK5njT--Hx3nF5sne5fleVfU-sZy6Za4B2U75PcE62oZgCPauOTAEm9Xuvrt5aMMovyzR8ecJZhm9bw7naU", + "p": "-BUGA-", + "kid": "a1", + "kty": "RSA"}, + { + "use": "sig", + "crv": "P-256", + "kty": "EC", + "y": "Yg4IRzHBMIsuQK2Oz0Uukp1aNDnpdoyk6QBMtmfGHQQ", + "x": "L0WUeVlc9r6YJd6ie9duvOU1RHwxSkJKA37IK9B4Bpc", + "kid": "a2" + }, + { + "use": "enc", + "crv": "P-256", + "kty": "EC", + "y": "E6E6g5_ziIZvfdAoACctnwOhuQYMvQzA259aftPn59M", + "x": "Yu8_BQE2L0f1MqnK0GumZOaj_77Tx70-LoudyRUnLM4", + "kid": "a3" + } + ] + } + client_json = JSON.parse(response.body) + expect(client_json["client_id"].length).to eq(32) + expect(client_json["ppid"]).to eq(true) + end + end + + context "when valid parameters with jwks_uri is passed" do + it "should return a client id" do + stub_request(:get, "http://example.com/uris") + .with(headers: {:Accept => "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "Host" => "example.com", :"User-Agent" => "Ruby"}) + .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) + stub_request(:get, "https://kentshikama.com/api/openid_connect/jwks.json") + .with(headers: {"Accept": "*/*", "Accept-Encoding": "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "Host": "kentshikama.com", "User-Agent": "Ruby"}) + .to_return(status: 200, + body: "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"qpW\",\"use\":\"sig\"}]}", headers: {}) + post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", + response_types: [], grant_types: [], application_type: "web", contacts: [], + logo_uri: "http://example.com/logo.png", client_uri: "http://example.com/client", + policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos", + sector_identifier_uri: "http://example.com/uris", subject_type: "pairwise", + token_endpoint_auth_method: "private_key_jwt", + jwks_uri: "https://kentshikama.com/api/openid_connect/jwks.json" + client_json = JSON.parse(response.body) + expect(client_json["client_id"].length).to eq(32) + expect(client_json["ppid"]).to eq(true) + end + end + context "when redirect uri is missing" do it "should return a invalid_client_metadata error" do post :create, response_types: [], grant_types: [], application_type: "web", contacts: [], diff --git a/spec/factories.rb b/spec/factories.rb index 0e7493360..7027fd57b 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -328,6 +328,13 @@ FactoryGirl.define do sector_identifier_uri "https://example.com/uri" end + factory :o_auth_application_with_ppid_with_specific_id, class: Api::OpenidConnect::OAuthApplication do + client_name "Diaspora Test Client" + redirect_uris %w(http://localhost:3000/) + ppid true + sector_identifier_uri "https://example.com/uri" + end + factory :o_auth_application_with_multiple_redirects, class: Api::OpenidConnect::OAuthApplication do client_name "Diaspora Test Client" redirect_uris %w(http://localhost:3000/ http://localhost/) diff --git a/spec/fixtures/jwks.json b/spec/fixtures/jwks.json new file mode 100644 index 000000000..be157aeec --- /dev/null +++ b/spec/fixtures/jwks.json @@ -0,0 +1 @@ +{"keys": [{"use": "enc", "e": "AQAB", "d": "lZQv0_81euRLeUYU84Aodh0ar7ymDlzWP5NMra4Jklkb-lTBWkI-u4RMsPqGYyW3KHRoL_pgzZXSzQx8RLQfER6timRWb--NxMMKllZubByU3RqH2ooNuocJurspYiXkznPW1Mg9DaNXL0C2hwWPQHTeUVISpjgi5TCOV1ccWVyksFruya_VNL1CIByB-L0GL1rqbKv32cDwi2A3_jJa61cpzfLSIBe-lvCO6tuiDsR4qgJnUwnndQFwEI_4mLmD3iNWXrc8N-poleV8mBfMqBB5fWwy_ZTFCpmQ5AywGmctaik_wNhMoWuA4tUfY6_1LdKld-5Cjq55eLtuJjtvuQ", "n": "tx3Hjdbc19lkTiohbJrNj4jf2_90MEE122CRrwtFu6saDywKcG7Bi7w2FMAK2oTkuWfqhWRb5BEGmnSXdiCEPO5d-ytqP3nwlZXHaCDYscpP8bB4YLhvCn7R8Efw6gwQle24QPRP3lYoFeuUbDUq7GKA5SfaZUvWoeWjqyLIaBspKQsC26_Umx1E4IXLrMSL6nkRnrYcVZBAXrYCeTP1XtsV38_lZVJfHSaJaUy4PKaj3yvgm93EV2CXybPti7CCMXZ34VqqWiF64pQjZsPu3ZTr7ha_TTQq499-zYRQNDvIVsBDLQQIgrbctuGqj6lrXb31Jj3JIEYqH_4h5X9d0Q", "q": "1q-r-bmMFbIzrLK2U3elksZq8CqUqZxlSfkGMZuVkxgYMS-e4FPzEp2iirG-eO11aa0cpMMoBdTnVdGJ_ZUR93w0lGf9XnQAJqxP7eOsrUoiW4VWlWH4WfOiLgpO-pFtyTz_JksYYaotc_Z3Zy-Szw6a39IDbuYGy1qL-15oQuc", "p": "2lrYPppRbcQWu4LtWN6tOVUrtCOPv1eLTKTc7q8vCMcem1Ox5QFB7KnUtNZ5Ni7wnZUeVDfimNebtjNsGvDSrpgIlo9dEnFBQsQIkzZ2SkoYfgmF8hNdi6P-BfRjdgYouy4c6xAnGDgSMTip1YnPRyvbMaoYT9E_tEcBW5wOeoc", "kid": "a0", "kty": "RSA"}, {"use": "sig", "e": "AQAB", "d": "DodXDEtkovWWGsMEXYy_nEEMCWyROMOebCnCv0ey3i4M4bh2dmwqgz0e-IKQAFlGiMkidGL1lNbq0uFS04FbuRAR06dYw1cbrNbDdhrWFxKTd1L5D9p-x-gW-YDWhpI8rUGRa76JXkOSxZUbg09_QyUd99CXAHh-FXi_ZkIKD8hK6FrAs68qhLf8MNkUv63DTduw7QgeFfQivdopePxyGuMk5n8veqwsUZsklQkhNlTYQqeM1xb2698ZQcNYkl0OssEsSJKRjXt-LRPowKrdvTuTo2p--HMI0pIEeFs7H_u5OW3jihjvoFClGPynHQhgWmQzlQRvWRXh6FhDVqFeGQ", "n": "zfZzttF7HmnTYwSMPdxKs5AoczbNS2mOPz-tN1g4ljqI_F1DG8cgQDcN_VDufxoFGRERo2FK6WEN41LhbGEyP6uL6wW6Cy29qE9QZcvY5mXrncndRSOkNcMizvuEJes_fMYrmP_lPiC6kWiqItTk9QBWqJfiYKhCx9cSDXsBmJXn3KWQCVHvj1ANFWW0CWLMKlWN-_NMNLIWJN_pEAocTZMzxSFBK1b5_5J8ZS7hfWRF6MQmjsJcz2jzA21SQZNpre3kwnTGRSwo05sAS-TyeadDqQPWgbqX69UzcGq5irhzN8cpZ_JaTk3Y_uV6owanTZLVvCgdjaAnMYeZhb0KFw", "q": "5E5XKK5njT-zzRqqTeY2tgP9PJBACeaH_xQRHZ_1ydE7tVd7HdgdaEHfQ1jvKIHFkknWWOBAY1mlBc4YDirLShB_voShD8C-Hx3nF5sne5fleVfU-sZy6Za4B2U75PcE62oZgCPauOTAEm9Xuvrt5aMMovyzR8ecJZhm9bw7naU", "p": "5vJHCSM3H3q4RltYzENC9RyZZV8EUmpkv9moyguT5t-BUGA-T4W_FGIxzOPXRWOckIplKkoDKhavUeNmTZMCUcue0nkICSJpvNE4Nb2p5PZk_QqSdQNvCasQtdojEG0AmfVD85SU551CYxJdLdDFOqyK2entpMr8lhokem189As", "kid": "a1", "kty": "RSA"}, {"use": "sig", "crv": "P-256", "kty": "EC", "y": "Yg4IRzHBMIsuQK2Oz0Uukp1aNDnpdoyk6QBMtmfGHQQ", "x": "L0WUeVlc9r6YJd6ie9duvOU1RHwxSkJKA37IK9B4Bpc", "kid": "a2"}, {"use": "enc", "crv": "P-256", "kty": "EC", "y": "E6E6g5_ziIZvfdAoACctnwOhuQYMvQzA259aftPn59M", "x": "Yu8_BQE2L0f1MqnK0GumZOaj_77Tx70-LoudyRUnLM4", "kid": "a3"}]} \ No newline at end of file diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index 66554b81e..c03aa4102 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -7,6 +7,16 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do o_auth_application: client, user: bob, redirect_uri: "http://localhost:3000/", scopes: ["openid"]) } let!(:code) { auth.create_code } + let!(:client_with_specific_id) { FactoryGirl.create(:o_auth_application_with_ppid_with_specific_id) } + let!(:auth_with_specific_id) do + client_with_specific_id.client_id = "14d692cd53d9c1a9f46fd69e0e57443e" + client_with_specific_id.jwks_file = jwks_file_path + client_with_specific_id.save! + Api::OpenidConnect::Authorization.find_or_create_by( + o_auth_application: client_with_specific_id, + user: bob, redirect_uri: "http://localhost:3000/", scopes: ["openid"]) + end + let!(:code_with_specific_id) { auth_with_specific_id.create_code } describe "the authorization code grant type" do context "when the authorization code is valid" do @@ -53,6 +63,44 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do end end + context "when the authorization code is valid with jwt bearer" do + before do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + redirect_uri: "http://localhost:3000/", code: code_with_specific_id, + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion: "eyJhbGciOiJSUzI1NiIsImtpZCI6ImExIn0.eyJhdWQiOiBbImh0dHBzOi8va2VudHNoaWthbWEuY29tL2FwaS9vcGVuaWRfY29ubmVjdC9hY2Nlc3NfdG9rZW5zIl0sICJpc3MiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UiLCAianRpIjogIjBtY3JyZVlIIiwgImV4cCI6IDE0NDMxNzA4OTEuMzk3NDU2LCAiaWF0IjogMTQ0MzE3MDI5MS4zOTc0NTYsICJzdWIiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UifQ.QJUR3SYFrEIlbfOKjO0NYInddklytbJ2LSWNpkQ1aNThgneDCVCjIYGCaL2C9Sw-GR8j7QSUsKOwBbjZMUmVPFTjsfB4wdgObbxVt1QAXwDjAXc5w1smOerRsoahZ4yKI1an6PTaFxMwnoXUQcBZTsOS6RgXOCPPPoxibxohxoehPLieM0l7LYcF5DQKg7fTxZYOpmtiP--nibJxomXdVQNLSnZuQwnyWtlp_gYmqrYMMN1LPSmNCgZMZZZIYttaaAIA96SylglqubowJRShtDO9rSvUz_sgeCo7qo5Bfb0B5n9_PtIlr1CZSVoHyYj2lVqQldx7fnGuqqQJCfDQog" + end + + it "should return a valid id token" do + json = JSON.parse(response.body) + encoded_id_token = json["id_token"] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, + Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY + expected_guid = bob.pairwise_pseudonymous_identifiers.find_by(identifier: "https://example.com/uri").guid + expect(decoded_token.sub).to eq(expected_guid) + expect(decoded_token.exp).to be > Time.zone.now.utc.to_i + end + + it "should return a valid access token" do + json = JSON.parse(response.body) + encoded_id_token = json["id_token"] + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, + Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY + access_token = json["access_token"] + access_token_check_num = UrlSafeBase64.encode64(OpenSSL::Digest::SHA256.digest(access_token)[0, 128 / 8]) + expect(decoded_token.at_hash).to eq(access_token_check_num) + end + + it "should not allow code to be reused" do + auth_with_specific_id.reload + expect(auth_with_specific_id.code).to eq(nil) + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + client_id: client.client_id, client_secret: client.client_secret, + redirect_uri: "http://localhost:3000/", code: code_with_specific_id + expect(JSON.parse(response.body)["error"]).to eq("invalid_grant") + end + end + context "when the authorization code is not valid" do it "should return an invalid grant error" do post api_openid_connect_access_tokens_path, grant_type: "authorization_code", @@ -61,6 +109,45 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do end end + context "when the client assertion is in an invalid format" do + before do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + redirect_uri: "http://localhost:3000/", code: code_with_specific_id, + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion: "invalid_client_assertion.random" + end + + it "should return an error" do + expect(response.body).to include "invalid_request" + end + end + + context "when the client assertion is not matching with jwks keys" do + before do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + redirect_uri: "http://localhost:3000/", code: code_with_specific_id, + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion: "eyJhbGciOiJSUzI1NiIsImtpZCI6ImExIn0.eyJhdWQiOiBbImh0dHBzOi8va2VudHNoaWthbWEuY29tL2FwaS9vcGVuaWRfY29ubmVjdC9hY2Nlc3NfdG9rZW5zIl0sICJpc3MiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UiLCAianRpIjogIjBtY3JyZVlIIiwgImV4cCI6IDE0NDMxNzA4OTEuMzk3NDU2LCAiaWF0IjogMTQ0MzE3MDI5MS4zOTc0NTYsICJzdWIiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UifQ.QJUR3SYFrEIlbfOKjO0NYInddklytbJ2LSWNpkQ1aNThgneDCVCjIYGCaL2C9Sw-GR8j7QSUsKOwBbjZMUmVPFTjsfB4wdgObbxVt1QAXwDjAXc5w1smOerRsoahZ4yKI1an6PTaFxMwnoXUQcBZTsOS6RgXOCPPPoxibxohxoehPLieM0l7LYcF5DQKg7fTxZYOpmtiP--nibJxomXdVQNLSnZuQwnyWtlp_gYmqrYMMN1LPSmNCgZMZZZIYttaaAIA96SylglqubowJRShtDO9rSvUz_sgeCo7qo5Bfb0B5n9_PtIlr1CZSVoHyYj2lVqQldx7fnGuqqQJCfDQoe" + end + + it "should return an error" do + expect(response.body).to include "invalid_grant" + end + end + + context "when kid doesn't exist in jwks keys" do + before do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + redirect_uri: "http://localhost:3000/", code: code_with_specific_id, + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion: "ewogIGFsZzogUlMyNTYsCiAga2lkOiBpbnZhbGlkX2tpZAp9Cg.eyJhdWQiOiBbImh0dHBzOi8va2VudHNoaWthbWEuY29tL2FwaS9vcGVuaWRfY29ubmVjdC9hY2Nlc3NfdG9rZW5zIl0sICJpc3MiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UiLCAianRpIjogIjBtY3JyZVlIIiwgImV4cCI6IDE0NDMxNzA4OTEuMzk3NDU2LCAiaWF0IjogMTQ0MzE3MDI5MS4zOTc0NTYsICJzdWIiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UifQ." + end + + it "should return an error" do + expect(response.body).to include "invalid_request" + end + end + context "when the client is unregistered" do it "should return an error" do post api_openid_connect_access_tokens_path, grant_type: "authorization_code", code: auth.refresh_token, @@ -69,6 +156,19 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do end end + context "when the client is unregistered with jwks keys" do + before do + post api_openid_connect_access_tokens_path, grant_type: "authorization_code", + redirect_uri: "http://localhost:3000/", code: code_with_specific_id, + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion: "eyJhbGciOiJSUzI1NiIsImtpZCI6ImExIn0.ewogIGF1ZDogWwogICAgaHR0cHM6Ly9rZW50c2hpa2FtYS5jb20vYXBpL29wZW5pZF9jb25uZWN0L2FjY2Vzc190b2tlbnMKICBdLAogIGlzczogMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2QsCiAganRpOiAwbWNycmVZSCwKICBleHA6IDE0NDMxNzA4OTEuMzk3NDU2LAogIGlhdDogMTQ0MzE3MDI5MS4zOTc0NTYsCiAgc3ViOiAxNGQ2OTJjZDUzZDljMWE5ZjQ2ZmQ2OWUwZTU3NDQzZAp9Cg.QJUR3SYFrEIlbfOKjO0NYInddklytbJ2LSWNpkQ1aNThgneDCVCjIYGCaL2C9Sw-GR8j7QSUsKOwBbjZMUmVPFTjsfB4wdgObbxVt1QAXwDjAXc5w1smOerRsoahZ4yKI1an6PTaFxMwnoXUQcBZTsOS6RgXOCPPPoxibxohxoehPLieM0l7LYcF5DQKg7fTxZYOpmtiP--nibJxomXdVQNLSnZuQwnyWtlp_gYmqrYMMN1LPSmNCgZMZZZIYttaaAIA96SylglqubowJRShtDO9rSvUz_sgeCo7qo5Bfb0B5n9_PtIlr1CZSVoHyYj2lVqQldx7fnGuqqQJCfDQog" + end + + it "should return an error" do + expect(response.body).to include "invalid_request" + end + end + context "when the code field is missing" do it "should return an invalid request error" do post api_openid_connect_access_tokens_path, grant_type: "authorization_code", diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ca444256a..4fb30af00 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -59,6 +59,10 @@ def photo_fixture_name @photo_fixture_name = File.join(File.dirname(__FILE__), "fixtures", "button.png") end +def jwks_file_path + @jwks_file = "../../spec/fixtures/jwks.json" +end + # Force fixture rebuild FileUtils.rm_f(Rails.root.join("tmp", "fixture_builder.yml")) From b3b9b39690eacd690d8b82c26c6f684d52a272b1 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sat, 3 Oct 2015 21:50:23 -0700 Subject: [PATCH 068/105] Fix request with prompt=none when not logged in --- app/controllers/api/openid_connect/authorizations_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 09d479f74..07929b38f 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -194,7 +194,7 @@ module Api def redirect_prompt_error_display(error, error_description) redirect_params_hash = {error: error, error_description: error_description, state: params[:state]} redirect_fragment = redirect_params_hash.compact.map {|key, value| key.to_s + "=" + value }.join("&") - redirect_to "#{params[:redirect_uri]}##{redirect_fragment}" + redirect_to params[:redirect_uri] + "?" + redirect_fragment end end end From da766d8e8b2c726b72bdb80f470423e05582f431 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sat, 3 Oct 2015 22:50:13 -0700 Subject: [PATCH 069/105] Revoke previously issued tokens on duplicate request --- app/models/api/openid_connect/authorization.rb | 11 ++++++++--- db/migrate/20150708153926_create_authorizations.rb | 1 + db/schema.rb | 5 +++-- spec/lib/api/openid_connect/token_endpoint_spec.rb | 2 -- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index 90ee38660..87cc05661 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -63,10 +63,15 @@ module Api def self.use_code(code) return unless code - find_by(code: code).tap do |auth| - next unless auth - auth.code = nil + auth = find_by(code: code) + return unless auth + if auth.code_used + auth.destroy + nil + else + auth.code_used = true auth.save + auth end end end diff --git a/db/migrate/20150708153926_create_authorizations.rb b/db/migrate/20150708153926_create_authorizations.rb index f964237d7..ee88ab017 100644 --- a/db/migrate/20150708153926_create_authorizations.rb +++ b/db/migrate/20150708153926_create_authorizations.rb @@ -8,6 +8,7 @@ class CreateAuthorizations < ActiveRecord::Migration t.string :redirect_uri t.string :nonce t.string :scopes + t.boolean :code_used, default: false t.timestamps null: false end diff --git a/db/schema.rb b/db/schema.rb index 8fce36389..17f436674 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -63,8 +63,9 @@ ActiveRecord::Schema.define(version: 20150828132451) do t.string "redirect_uri", limit: 255 t.string "nonce", limit: 255 t.string "scopes", limit: 255 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.boolean "code_used", default: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "authorizations", ["o_auth_application_id"], name: "index_authorizations_on_o_auth_application_id", using: :btree diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index c03aa4102..98887932f 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -48,7 +48,6 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do it "should not allow code to be reused" do auth.reload - expect(auth.code).to eq(nil) post api_openid_connect_access_tokens_path, grant_type: "authorization_code", client_id: client.client_id, client_secret: client.client_secret, redirect_uri: "http://localhost:3000/", code: code @@ -93,7 +92,6 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do it "should not allow code to be reused" do auth_with_specific_id.reload - expect(auth_with_specific_id.code).to eq(nil) post api_openid_connect_access_tokens_path, grant_type: "authorization_code", client_id: client.client_id, client_secret: client.client_secret, redirect_uri: "http://localhost:3000/", code: code_with_specific_id From 80cbc7d915bef972aff30a9a9d92e25b88d72397 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 4 Oct 2015 00:36:13 -0700 Subject: [PATCH 070/105] Destroy previous auths on new auth request --- .../api/openid_connect/authorizations_controller.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 07929b38f..e89b72764 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -10,6 +10,12 @@ module Api def new auth = Api::OpenidConnect::Authorization.find_by_client_id_and_user(params[:client_id], current_user) + if auth + auth.o_auth_access_tokens.destroy_all + auth.id_tokens.destroy_all + auth.code_used = false + auth.save + end if logged_in_before?(params[:max_age]) reauthenticate elsif params[:prompt] From 2c7d102019eec80b38f4aff72456aa4fa7583a06 Mon Sep 17 00:00:00 2001 From: augier Date: Fri, 9 Oct 2015 16:22:05 +0200 Subject: [PATCH 071/105] Design for authorization page when client_name not providen + XSS spec --- app/assets/stylesheets/mobile/settings.scss | 12 +++++++ .../authorizations_controller.rb | 6 +--- app/helpers/user_applications_helper.rb | 9 +++++ app/presenters/user_application_presenter.rb | 36 +++++++++++++++++++ app/presenters/user_applications_presenter.rb | 28 +++------------ .../authorizations/_grants_list.haml | 17 ++++----- .../authorizations/new.html.haml | 4 +-- .../_add_remove_applications.haml | 2 +- .../user_applications/_grants_list.haml | 16 ++++----- .../user_applications/index.html.haml | 22 +++++------- .../user_applications/index.mobile.haml | 22 +++++------- config/locales/diaspora/en.yml | 1 - features/desktop/user_applications.feature | 4 +++ .../user_applications_steps.rb | 6 ++++ .../authorizations_controller_spec.rb | 1 + 15 files changed, 111 insertions(+), 75 deletions(-) create mode 100644 app/helpers/user_applications_helper.rb create mode 100644 app/presenters/user_application_presenter.rb diff --git a/app/assets/stylesheets/mobile/settings.scss b/app/assets/stylesheets/mobile/settings.scss index 45b7c633e..76bbbffb2 100644 --- a/app/assets/stylesheets/mobile/settings.scss +++ b/app/assets/stylesheets/mobile/settings.scss @@ -33,3 +33,15 @@ .applications-page .applications-explanation { margin-bottom: 15px; } + +.application-img { + margin: auto; + max-width: 150px; + text-align: center; + + .entypo-browser { + font-size: 137px; + height: 160px; + margin-top: -45px; + } +} diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index e89b72764..3176b2981 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -114,11 +114,7 @@ module Api ] save_request_parameters - @app = { - name: @o_auth_application.client_name, - image: @o_auth_application.image_uri, - authorizations: @scopes - } + @app = UserApplicationPresenter.new @o_auth_application, @scopes render :new end diff --git a/app/helpers/user_applications_helper.rb b/app/helpers/user_applications_helper.rb new file mode 100644 index 000000000..6fe2a7f57 --- /dev/null +++ b/app/helpers/user_applications_helper.rb @@ -0,0 +1,9 @@ +module UserApplicationsHelper + def user_application_name(app) + if app.name? + "#{app.name} (#{link_to(app.url, app.url)})" + else + link_to(app.url, app.url) + end + end +end diff --git a/app/presenters/user_application_presenter.rb b/app/presenters/user_application_presenter.rb new file mode 100644 index 000000000..17dc1b1b2 --- /dev/null +++ b/app/presenters/user_application_presenter.rb @@ -0,0 +1,36 @@ +class UserApplicationPresenter + def initialize(application, scopes, authorization_id=nil) + @app = application + @scopes = scopes + @authorization_id = authorization_id + end + + def scopes + @scopes + end + + def id + @authorization_id + end + + def name + @app.client_name + end + + def image + @app.image_uri + end + + def name? + if @app.client_name + true + else + false + end + end + + def url + client_redirect = URI(@app.redirect_uris[0]) + "#{client_redirect.scheme}://#{client_redirect.host}" + end +end diff --git a/app/presenters/user_applications_presenter.rb b/app/presenters/user_applications_presenter.rb index fe94a73b0..f04b97394 100644 --- a/app/presenters/user_applications_presenter.rb +++ b/app/presenters/user_applications_presenter.rb @@ -4,7 +4,10 @@ class UserApplicationsPresenter end def user_applications - @applications ||= @user.o_auth_applications.map {|app| app_as_json(app) } + @applications ||= @user.o_auth_applications.map do |app| + authorization = Api::OpenidConnect::Authorization.find_by_client_id_and_user(app.client_id, @user) + UserApplicationPresenter.new app, authorization.scopes, authorization.id + end end def applications_count @@ -14,27 +17,4 @@ class UserApplicationsPresenter def applications? applications_count > 0 end - - private - - def app_as_json(application) - { - id: find_id(application), - name: application.client_name, - image: application.image_uri, - authorizations: find_scopes(application) - } - end - - def find_scopes(application) - find_auth(application).scopes - end - - def find_id(application) - find_auth(application).id - end - - def find_auth(application) - Api::OpenidConnect::Authorization.find_by_client_id_and_user(application.client_id, @user) - end end diff --git a/app/views/api/openid_connect/authorizations/_grants_list.haml b/app/views/api/openid_connect/authorizations/_grants_list.haml index 72c183221..4f858457a 100644 --- a/app/views/api/openid_connect/authorizations/_grants_list.haml +++ b/app/views/api/openid_connect/authorizations/_grants_list.haml @@ -1,16 +1,17 @@ .application-img - - if app[:image] - = image_tag app[:image], class: "img-responsive" + - if app.image + = image_tag app.image, class: "img-responsive" - else %i.entypo-browser .application-authorizations - - if app[:authorizations].count > 0 - %h4= t("api.openid_connect.authorizations.new.access", name: app[:name]) + - if app.scopes.count > 0 + %h4 + = t("api.openid_connect.authorizations.new.access", name: user_application_name(app)).html_safe %ul - - app[:authorizations].each do |authorization| + - app.scopes.each do |scope| %li - %b= t("api.openid_connect.scopes.#{authorization}.name") - %p= t("api.openid_connect.scopes.#{authorization}.description") + %b= t("api.openid_connect.scopes.#{scope}.name") + %p= t("api.openid_connect.scopes.#{scope}.description") - else .well - = t("api.openid_connect.authorizations.new.no_requirement", name: app[:name]) + = t("api.openid_connect.authorizations.new.no_requirement", name: user_application_name(app)).html_safe diff --git a/app/views/api/openid_connect/authorizations/new.html.haml b/app/views/api/openid_connect/authorizations/new.html.haml index e292f3125..e7bc33028 100644 --- a/app/views/api/openid_connect/authorizations/new.html.haml +++ b/app/views/api/openid_connect/authorizations/new.html.haml @@ -1,6 +1,6 @@ -.user-consent.col-md-6.col-md-offset-1 +.user-consent.col-md-10.col-md-offset-1 %ul.list-group - %li.list-group-item.authorized-application + %li.list-group-item.authorized-application.clearfix = render "grants_list", app: @app .clearfix.pull-right diff --git a/app/views/api/openid_connect/user_applications/_add_remove_applications.haml b/app/views/api/openid_connect/user_applications/_add_remove_applications.haml index 4837111f6..ff467c3f2 100644 --- a/app/views/api/openid_connect/user_applications/_add_remove_applications.haml +++ b/app/views/api/openid_connect/user_applications/_add_remove_applications.haml @@ -3,7 +3,7 @@ - @user_apps.user_applications.each do |app| %li.list-group-item.authorized-application = render "grants_list", app: app - = form_for "application", url: "#{api_openid_connect_authorizations_path}/#{app[:id]}", + = form_for "application", url: "#{api_openid_connect_authorization_path(app.id)}", html: {method: :delete, class: "form-horizontal"} do |f| .clearfix= f.submit t("api.openid_connect.user_applications.revoke_autorization"), class: "btn btn-danger pull-right app-revoke" diff --git a/app/views/api/openid_connect/user_applications/_grants_list.haml b/app/views/api/openid_connect/user_applications/_grants_list.haml index 4b11c0030..eb874053c 100644 --- a/app/views/api/openid_connect/user_applications/_grants_list.haml +++ b/app/views/api/openid_connect/user_applications/_grants_list.haml @@ -1,16 +1,16 @@ .application-img - - if app[:image] - = image_tag app[:image], class: "img-responsive" + - if app.image + = image_tag app.image, class: "img-responsive" - else %i.entypo-browser .application-authorizations - - if app[:authorizations].count > 0 - %h4= t("api.openid_connect.user_applications.index.access", name: app[:name]) + - if app.scopes.count > 0 + %h4= t("api.openid_connect.user_applications.index.access", name: user_application_name(app)).html_safe %ul - - app[:authorizations].each do |authorization| + - app.scopes.each do |scope| %li - %b= t("api.openid_connect.scopes.#{authorization}.name") - %p= t("api.openid_connect.scopes.#{authorization}.description") + %b= t("api.openid_connect.scopes.#{scope}.name") + %p= t("api.openid_connect.scopes.#{scope}.description") - else .well - = t("api.openid_connect.user_applications.index.no_requirement", name: app[:name]) + = t("api.openid_connect.user_applications.index.no_requirement", name: user_application_name(app)).html_safe diff --git a/app/views/api/openid_connect/user_applications/index.html.haml b/app/views/api/openid_connect/user_applications/index.html.haml index 07ebf6a24..ea2241b74 100644 --- a/app/views/api/openid_connect/user_applications/index.html.haml +++ b/app/views/api/openid_connect/user_applications/index.html.haml @@ -2,16 +2,12 @@ = t(".edit_applications") .container-fluid.applications-page - = render "shared/settings_nav" - .container-fluid - .row - .col-lg-8.col-lg-offset-2 - %h3= t(".title") - %p.visible-sm-block.visible-xs-block - = t(".applications_explanation") - .row - .col-md-7 - = render "add_remove_applications" - .col-md-5 - %p.hidden-sm.hidden-xs - = t(".applications_explanation") + .row + .col-lg-10.col-lg-offset-1 + = render "shared/settings_nav" + .row + .col-lg-8.col-lg-offset-2 + %h3= t(".title") + .row + .col-md-12 + = render "add_remove_applications" diff --git a/app/views/api/openid_connect/user_applications/index.mobile.haml b/app/views/api/openid_connect/user_applications/index.mobile.haml index da89efde1..9e75a012d 100644 --- a/app/views/api/openid_connect/user_applications/index.mobile.haml +++ b/app/views/api/openid_connect/user_applications/index.mobile.haml @@ -1,13 +1,9 @@ -.settings_container.applications-page - - content_for :page_title do - = t(".edit_applications") - - = render "shared/settings_nav" - - .container-fluid - .row - .col-md-12.applications-explanation - = t(".applications_explanation") - .col-md-12 - = render "add_remove_applications" - +.container-fluid.settings_container.applications-page + .row + .col-lg-10.col-lg-offset-1 + - content_for :page_title do + = t(".edit_applications") + = render "shared/settings_nav" + .row + .col-md-12 + = render "add_remove_applications" diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 89095b5cf..3539d69fd 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -898,7 +898,6 @@ en: title: "Authorized applications" access: "%{name} has access to:" no_requirement: "%{name} requires no permissions" - applications_explanation: "Here is a list of applications you have authorized" no_applications: "You have no authorized applications" revoke_autorization: "Revoke" scopes: diff --git a/features/desktop/user_applications.feature b/features/desktop/user_applications.feature index b1147ae1a..ac51b4d4c 100644 --- a/features/desktop/user_applications.feature +++ b/features/desktop/user_applications.feature @@ -21,3 +21,7 @@ Feature: managing authorized applications Then I should see 1 authorized applications And I revoke the first authorization Then I should see 0 authorized applications + + Scenario: XSS escaping + When An application manually registers + Then I should not see "" diff --git a/features/step_definitions/user_applications_steps.rb b/features/step_definitions/user_applications_steps.rb index 7cef79050..afbae1930 100644 --- a/features/step_definitions/user_applications_steps.rb +++ b/features/step_definitions/user_applications_steps.rb @@ -14,3 +14,9 @@ end When /^I revoke the first authorization$/ do find(".app-revoke", match: :first).click end + +When /^An application manually registers$/ do + post api_openid_connect_authorizations_new_path, client_name: "", + redirect_uri: "http://example.org/", response_type: "id_token", scope: "openid", + state: 1234, display: "page", prompt: "none" +end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 7527bacce..9c07d9177 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -130,6 +130,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do end end end + context "when already authorized" do let!(:auth) { Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice, From 9c9880d8807ecc964f72ef91b2429363687f9c7e Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 4 Oct 2015 01:57:54 -0700 Subject: [PATCH 072/105] Move JWKs files to database --- .gitignore | 1 - .../authorizations_controller.rb | 15 +++++++------ .../token_endpoint_controller.rb | 6 ++---- .../api/openid_connect/o_auth_application.rb | 18 +++------------- ...150613202109_create_o_auth_applications.rb | 3 +-- db/schema.rb | 10 ++++----- ...t_assertion_with_nonexistent_client_id.txt | 1 + .../client_assertion_with_nonexistent_kid.txt | 1 + .../client_assertion_with_tampered_sig.txt | 1 + spec/fixtures/valid_client_assertion.txt | 1 + .../api/openid_connect/token_endpoint_spec.rb | 10 ++++----- spec/spec_helper.rb | 21 ++++++++++++++++++- 12 files changed, 49 insertions(+), 39 deletions(-) create mode 100644 spec/fixtures/client_assertion_with_nonexistent_client_id.txt create mode 100644 spec/fixtures/client_assertion_with_nonexistent_kid.txt create mode 100644 spec/fixtures/client_assertion_with_tampered_sig.txt create mode 100644 spec/fixtures/valid_client_assertion.txt diff --git a/.gitignore b/.gitignore index 3f1d9c02a..3fc25bd96 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,6 @@ config/database.yml .rvmrc_custom .rvmrc.local config/oidc_key.pem -config/jwks/ # Mailing list stuff config/email_offset diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 3176b2981..e8369e372 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -10,12 +10,7 @@ module Api def new auth = Api::OpenidConnect::Authorization.find_by_client_id_and_user(params[:client_id], current_user) - if auth - auth.o_auth_access_tokens.destroy_all - auth.id_tokens.destroy_all - auth.code_used = false - auth.save - end + reset_auth(auth) if logged_in_before?(params[:max_age]) reauthenticate elsif params[:prompt] @@ -43,6 +38,14 @@ module Api private + def reset_auth(auth) + return unless auth + auth.o_auth_access_tokens.destroy_all + auth.id_tokens.destroy_all + auth.code_used = false + auth.save + end + def handle_prompt(prompt, auth) if prompt.include? "select_account" handle_prompt_params_error("account_selection_required", diff --git a/app/controllers/api/openid_connect/token_endpoint_controller.rb b/app/controllers/api/openid_connect/token_endpoint_controller.rb index 86d3d2f79..cfa06638f 100644 --- a/app/controllers/api/openid_connect/token_endpoint_controller.rb +++ b/app/controllers/api/openid_connect/token_endpoint_controller.rb @@ -6,7 +6,7 @@ module Api if req["client_assertion_type"] == "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" handle_jwt_bearer(req) end - self.status, self.response.headers, self.response_body = Api::OpenidConnect::TokenEndpoint.new.call(request.env) + self.status, response.headers, self.response_body = Api::OpenidConnect::TokenEndpoint.new.call(request.env) nil end @@ -24,12 +24,10 @@ module Api end def fetch_public_key(o_auth_app, jwt) - jwks_file_path = File.join(Rails.root, "config", "jwks", o_auth_app.jwks_file) - public_key = fetch_public_key_from_json(File.read(jwks_file_path), jwt) + public_key = fetch_public_key_from_json(o_auth_app.jwks, jwt) if public_key.empty? && o_auth_app.jwks_uri uri = URI.parse(o_auth_app.jwks_uri) response = Net::HTTP.get_response(uri) - File.write jwks_file_path, response.body public_key = fetch_public_key_from_json(response.body, jwt) end raise Rack::OAuth2::Server::Authorize::BadRequest(:unauthorized_client) if public_key.empty? diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index a89fa0c05..0004a93ad 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -10,7 +10,7 @@ module Api validates :client_secret, presence: true validates :client_name, uniqueness: {scope: :redirect_uris} - %i(redirect_uris response_types grant_types contacts).each do |serializable| + %i(redirect_uris response_types grant_types contacts jwks).each do |serializable| serialize serializable, JSON end @@ -82,27 +82,15 @@ module Api elsif key == :jwks_uri uri = URI.parse(value) response = Net::HTTP.get_response(uri) - file_name = create_file_path(response.body) - attr[:jwks_file] = file_name + ".json" + attr[:jwks] = response.body attr[:jwks_uri] = value elsif key == :jwks - file_name = create_file_path(value.to_json) - attr[:jwks_file] = file_name + ".json" + attr[:jwks] = value.to_json else attr[key] = value end end end - - def create_file_path(content) - file_name = Base64.urlsafe_encode64(Digest::SHA256.base64digest(content)) - directory_name = File.join(Rails.root, "config", "jwks") - Dir.mkdir(directory_name) unless File.exist?(directory_name) - jwk_file_path = File.join(Rails.root, "config", "jwks", file_name + ".json") - File.write jwk_file_path, content - File.chmod(0600, jwk_file_path) - file_name - end end end end diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index 4b4db75f2..f0fd5ca67 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -17,9 +17,8 @@ class CreateOAuthApplications < ActiveRecord::Migration t.string :tos_uri t.string :sector_identifier_uri t.string :token_endpoint_auth_method + t.text :jwks t.string :jwks_uri - t.string :jwks_file - t.boolean :ppid, default: false t.timestamps null: false diff --git a/db/schema.rb b/db/schema.rb index 17f436674..a7232a417 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -281,7 +281,7 @@ ActiveRecord::Schema.define(version: 20150828132451) do t.string "redirect_uris", limit: 255 t.string "response_types", limit: 255 t.string "grant_types", limit: 255 - t.string "application_type", limit: 255, default: "web" + t.string "application_type", limit: 255, default: "web" t.string "contacts", limit: 255 t.string "logo_uri", limit: 255 t.string "client_uri", limit: 255 @@ -289,11 +289,11 @@ ActiveRecord::Schema.define(version: 20150828132451) do t.string "tos_uri", limit: 255 t.string "sector_identifier_uri", limit: 255 t.string "token_endpoint_auth_method", limit: 255 + t.text "jwks", limit: 65535 t.string "jwks_uri", limit: 255 - t.string "jwks_file", limit: 255 - t.boolean "ppid", default: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.boolean "ppid", default: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "o_auth_applications", ["client_id"], name: "index_o_auth_applications_on_client_id", unique: true, length: {"client_id"=>191}, using: :btree diff --git a/spec/fixtures/client_assertion_with_nonexistent_client_id.txt b/spec/fixtures/client_assertion_with_nonexistent_client_id.txt new file mode 100644 index 000000000..3bcabb078 --- /dev/null +++ b/spec/fixtures/client_assertion_with_nonexistent_client_id.txt @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsImtpZCI6ImExIn0.ewogIGF1ZDogWwogICAgaHR0cHM6Ly9rZW50c2hpa2FtYS5jb20vYXBpL29wZW5pZF9jb25uZWN0L2FjY2Vzc190b2tlbnMKICBdLAogIGlzczogMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2QsCiAganRpOiAwbWNycmVZSCwKICBleHA6IDE0NDMxNzA4OTEuMzk3NDU2LAogIGlhdDogMTQ0MzE3MDI5MS4zOTc0NTYsCiAgc3ViOiAxNGQ2OTJjZDUzZDljMWE5ZjQ2ZmQ2OWUwZTU3NDQzZAp9Cg.QJUR3SYFrEIlbfOKjO0NYInddklytbJ2LSWNpkQ1aNThgneDCVCjIYGCaL2C9Sw-GR8j7QSUsKOwBbjZMUmVPFTjsfB4wdgObbxVt1QAXwDjAXc5w1smOerRsoahZ4yKI1an6PTaFxMwnoXUQcBZTsOS6RgXOCPPPoxibxohxoehPLieM0l7LYcF5DQKg7fTxZYOpmtiP--nibJxomXdVQNLSnZuQwnyWtlp_gYmqrYMMN1LPSmNCgZMZZZIYttaaAIA96SylglqubowJRShtDO9rSvUz_sgeCo7qo5Bfb0B5n9_PtIlr1CZSVoHyYj2lVqQldx7fnGuqqQJCfDQog diff --git a/spec/fixtures/client_assertion_with_nonexistent_kid.txt b/spec/fixtures/client_assertion_with_nonexistent_kid.txt new file mode 100644 index 000000000..3419d02c1 --- /dev/null +++ b/spec/fixtures/client_assertion_with_nonexistent_kid.txt @@ -0,0 +1 @@ +ewogIGFsZzogUlMyNTYsCiAga2lkOiBpbnZhbGlkX2tpZAp9Cg.eyJhdWQiOiBbImh0dHBzOi8va2VudHNoaWthbWEuY29tL2FwaS9vcGVuaWRfY29ubmVjdC9hY2Nlc3NfdG9rZW5zIl0sICJpc3MiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UiLCAianRpIjogIjBtY3JyZVlIIiwgImV4cCI6IDE0NDMxNzA4OTEuMzk3NDU2LCAiaWF0IjogMTQ0MzE3MDI5MS4zOTc0NTYsICJzdWIiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UifQ. \ No newline at end of file diff --git a/spec/fixtures/client_assertion_with_tampered_sig.txt b/spec/fixtures/client_assertion_with_tampered_sig.txt new file mode 100644 index 000000000..ff225126e --- /dev/null +++ b/spec/fixtures/client_assertion_with_tampered_sig.txt @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsImtpZCI6ImExIn0.eyJhdWQiOiBbImh0dHBzOi8va2VudHNoaWthbWEuY29tL2FwaS9vcGVuaWRfY29ubmVjdC9hY2Nlc3NfdG9rZW5zIl0sICJpc3MiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UiLCAianRpIjogIjBtY3JyZVlIIiwgImV4cCI6IDE0NDMxNzA4OTEuMzk3NDU2LCAiaWF0IjogMTQ0MzE3MDI5MS4zOTc0NTYsICJzdWIiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UifQ.QJUR3SYFrEIlbfOKjO0NYInddklytbJ2LSWNpkQ1aNThgneDCVCjIYGCaL2C9Sw-GR8j7QSUsKOwBbjZMUmVPFTjsfB4wdgObbxVt1QAXwDjAXc5w1smOerRsoahZ4yKI1an6PTaFxMwnoXUQcBZTsOS6RgXOCPPPoxibxohxoehPLieM0l7LYcF5DQKg7fTxZYOpmtiP--nibJxomXdVQNLSnZuQwnyWtlp_gYmqrYMMN1LPSmNCgZMZZZIYttaaAIA96SylglqubowJRShtDO9rSvUz_sgeCo7qo5Bfb0B5n9_PtIlr1CZSVoHyYj2lVqQldx7fnGuqqQJCfDQoe \ No newline at end of file diff --git a/spec/fixtures/valid_client_assertion.txt b/spec/fixtures/valid_client_assertion.txt new file mode 100644 index 000000000..4a0ac9441 --- /dev/null +++ b/spec/fixtures/valid_client_assertion.txt @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsImtpZCI6ImExIn0.eyJhdWQiOiBbImh0dHBzOi8va2VudHNoaWthbWEuY29tL2FwaS9vcGVuaWRfY29ubmVjdC9hY2Nlc3NfdG9rZW5zIl0sICJpc3MiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UiLCAianRpIjogIjBtY3JyZVlIIiwgImV4cCI6IDE0NDMxNzA4OTEuMzk3NDU2LCAiaWF0IjogMTQ0MzE3MDI5MS4zOTc0NTYsICJzdWIiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UifQ.QJUR3SYFrEIlbfOKjO0NYInddklytbJ2LSWNpkQ1aNThgneDCVCjIYGCaL2C9Sw-GR8j7QSUsKOwBbjZMUmVPFTjsfB4wdgObbxVt1QAXwDjAXc5w1smOerRsoahZ4yKI1an6PTaFxMwnoXUQcBZTsOS6RgXOCPPPoxibxohxoehPLieM0l7LYcF5DQKg7fTxZYOpmtiP--nibJxomXdVQNLSnZuQwnyWtlp_gYmqrYMMN1LPSmNCgZMZZZIYttaaAIA96SylglqubowJRShtDO9rSvUz_sgeCo7qo5Bfb0B5n9_PtIlr1CZSVoHyYj2lVqQldx7fnGuqqQJCfDQog \ No newline at end of file diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index 98887932f..6d944626f 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -10,7 +10,7 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do let!(:client_with_specific_id) { FactoryGirl.create(:o_auth_application_with_ppid_with_specific_id) } let!(:auth_with_specific_id) do client_with_specific_id.client_id = "14d692cd53d9c1a9f46fd69e0e57443e" - client_with_specific_id.jwks_file = jwks_file_path + client_with_specific_id.jwks = File.read(jwks_file_path) client_with_specific_id.save! Api::OpenidConnect::Authorization.find_or_create_by( o_auth_application: client_with_specific_id, @@ -67,7 +67,7 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do post api_openid_connect_access_tokens_path, grant_type: "authorization_code", redirect_uri: "http://localhost:3000/", code: code_with_specific_id, client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", - client_assertion: "eyJhbGciOiJSUzI1NiIsImtpZCI6ImExIn0.eyJhdWQiOiBbImh0dHBzOi8va2VudHNoaWthbWEuY29tL2FwaS9vcGVuaWRfY29ubmVjdC9hY2Nlc3NfdG9rZW5zIl0sICJpc3MiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UiLCAianRpIjogIjBtY3JyZVlIIiwgImV4cCI6IDE0NDMxNzA4OTEuMzk3NDU2LCAiaWF0IjogMTQ0MzE3MDI5MS4zOTc0NTYsICJzdWIiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UifQ.QJUR3SYFrEIlbfOKjO0NYInddklytbJ2LSWNpkQ1aNThgneDCVCjIYGCaL2C9Sw-GR8j7QSUsKOwBbjZMUmVPFTjsfB4wdgObbxVt1QAXwDjAXc5w1smOerRsoahZ4yKI1an6PTaFxMwnoXUQcBZTsOS6RgXOCPPPoxibxohxoehPLieM0l7LYcF5DQKg7fTxZYOpmtiP--nibJxomXdVQNLSnZuQwnyWtlp_gYmqrYMMN1LPSmNCgZMZZZIYttaaAIA96SylglqubowJRShtDO9rSvUz_sgeCo7qo5Bfb0B5n9_PtIlr1CZSVoHyYj2lVqQldx7fnGuqqQJCfDQog" + client_assertion: File.read(valid_client_assertion_path) end it "should return a valid id token" do @@ -125,7 +125,7 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do post api_openid_connect_access_tokens_path, grant_type: "authorization_code", redirect_uri: "http://localhost:3000/", code: code_with_specific_id, client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", - client_assertion: "eyJhbGciOiJSUzI1NiIsImtpZCI6ImExIn0.eyJhdWQiOiBbImh0dHBzOi8va2VudHNoaWthbWEuY29tL2FwaS9vcGVuaWRfY29ubmVjdC9hY2Nlc3NfdG9rZW5zIl0sICJpc3MiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UiLCAianRpIjogIjBtY3JyZVlIIiwgImV4cCI6IDE0NDMxNzA4OTEuMzk3NDU2LCAiaWF0IjogMTQ0MzE3MDI5MS4zOTc0NTYsICJzdWIiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UifQ.QJUR3SYFrEIlbfOKjO0NYInddklytbJ2LSWNpkQ1aNThgneDCVCjIYGCaL2C9Sw-GR8j7QSUsKOwBbjZMUmVPFTjsfB4wdgObbxVt1QAXwDjAXc5w1smOerRsoahZ4yKI1an6PTaFxMwnoXUQcBZTsOS6RgXOCPPPoxibxohxoehPLieM0l7LYcF5DQKg7fTxZYOpmtiP--nibJxomXdVQNLSnZuQwnyWtlp_gYmqrYMMN1LPSmNCgZMZZZIYttaaAIA96SylglqubowJRShtDO9rSvUz_sgeCo7qo5Bfb0B5n9_PtIlr1CZSVoHyYj2lVqQldx7fnGuqqQJCfDQoe" + client_assertion: File.read(client_assertion_with_tampered_sig_path) end it "should return an error" do @@ -138,7 +138,7 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do post api_openid_connect_access_tokens_path, grant_type: "authorization_code", redirect_uri: "http://localhost:3000/", code: code_with_specific_id, client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", - client_assertion: "ewogIGFsZzogUlMyNTYsCiAga2lkOiBpbnZhbGlkX2tpZAp9Cg.eyJhdWQiOiBbImh0dHBzOi8va2VudHNoaWthbWEuY29tL2FwaS9vcGVuaWRfY29ubmVjdC9hY2Nlc3NfdG9rZW5zIl0sICJpc3MiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UiLCAianRpIjogIjBtY3JyZVlIIiwgImV4cCI6IDE0NDMxNzA4OTEuMzk3NDU2LCAiaWF0IjogMTQ0MzE3MDI5MS4zOTc0NTYsICJzdWIiOiAiMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2UifQ." + client_assertion: File.read(client_assertion_with_nonexistent_kid_path) end it "should return an error" do @@ -159,7 +159,7 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do post api_openid_connect_access_tokens_path, grant_type: "authorization_code", redirect_uri: "http://localhost:3000/", code: code_with_specific_id, client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", - client_assertion: "eyJhbGciOiJSUzI1NiIsImtpZCI6ImExIn0.ewogIGF1ZDogWwogICAgaHR0cHM6Ly9rZW50c2hpa2FtYS5jb20vYXBpL29wZW5pZF9jb25uZWN0L2FjY2Vzc190b2tlbnMKICBdLAogIGlzczogMTRkNjkyY2Q1M2Q5YzFhOWY0NmZkNjllMGU1NzQ0M2QsCiAganRpOiAwbWNycmVZSCwKICBleHA6IDE0NDMxNzA4OTEuMzk3NDU2LAogIGlhdDogMTQ0MzE3MDI5MS4zOTc0NTYsCiAgc3ViOiAxNGQ2OTJjZDUzZDljMWE5ZjQ2ZmQ2OWUwZTU3NDQzZAp9Cg.QJUR3SYFrEIlbfOKjO0NYInddklytbJ2LSWNpkQ1aNThgneDCVCjIYGCaL2C9Sw-GR8j7QSUsKOwBbjZMUmVPFTjsfB4wdgObbxVt1QAXwDjAXc5w1smOerRsoahZ4yKI1an6PTaFxMwnoXUQcBZTsOS6RgXOCPPPoxibxohxoehPLieM0l7LYcF5DQKg7fTxZYOpmtiP--nibJxomXdVQNLSnZuQwnyWtlp_gYmqrYMMN1LPSmNCgZMZZZIYttaaAIA96SylglqubowJRShtDO9rSvUz_sgeCo7qo5Bfb0B5n9_PtIlr1CZSVoHyYj2lVqQldx7fnGuqqQJCfDQog" + client_assertion: File.read(client_assertion_with_nonexistent_client_id_path) end it "should return an error" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4fb30af00..b97fb6166 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -60,7 +60,26 @@ def photo_fixture_name end def jwks_file_path - @jwks_file = "../../spec/fixtures/jwks.json" + @jwks_file = File.join(File.dirname(__FILE__), "fixtures", "jwks.json") +end + +def valid_client_assertion_path + @valid_client_assertion = File.join(File.dirname(__FILE__), "fixtures", "valid_client_assertion.txt") +end + +def client_assertion_with_tampered_sig_path + @client_assertion_with_tampered_sig = File.join(File.dirname(__FILE__), "fixtures", + "client_assertion_with_tampered_sig.txt") +end + +def client_assertion_with_nonexistent_kid_path + @client_assertion_with_nonexistent_kid = File.join(File.dirname(__FILE__), "fixtures", + "client_assertion_with_nonexistent_kid.txt") +end + +def client_assertion_with_nonexistent_client_id_path + @client_assertion_with_nonexistent_client_id = File.join(File.dirname(__FILE__), "fixtures", + "client_assertion_with_nonexistent_client_id.txt") end # Force fixture rebuild From 6fcb9a9d3a2ba05443b2d9674cab52ae3f8ba63f Mon Sep 17 00:00:00 2001 From: augier Date: Sat, 10 Oct 2015 12:10:29 +0200 Subject: [PATCH 073/105] Add XSS spec for application's name --- app/presenters/user_application_presenter.rb | 2 +- features/desktop/user_applications.feature | 4 ---- .../step_definitions/user_applications_steps.rb | 6 ------ .../authorizations_controller_spec.rb | 1 - .../openid_connect/user_applications_spec.rb | 17 +++++++++++++++++ spec/factories.rb | 5 +++++ 6 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 spec/controllers/api/openid_connect/user_applications_spec.rb diff --git a/app/presenters/user_application_presenter.rb b/app/presenters/user_application_presenter.rb index 17dc1b1b2..8a0015622 100644 --- a/app/presenters/user_application_presenter.rb +++ b/app/presenters/user_application_presenter.rb @@ -14,7 +14,7 @@ class UserApplicationPresenter end def name - @app.client_name + CGI::escape @app.client_name end def image diff --git a/features/desktop/user_applications.feature b/features/desktop/user_applications.feature index ac51b4d4c..b1147ae1a 100644 --- a/features/desktop/user_applications.feature +++ b/features/desktop/user_applications.feature @@ -21,7 +21,3 @@ Feature: managing authorized applications Then I should see 1 authorized applications And I revoke the first authorization Then I should see 0 authorized applications - - Scenario: XSS escaping - When An application manually registers - Then I should not see "" diff --git a/features/step_definitions/user_applications_steps.rb b/features/step_definitions/user_applications_steps.rb index afbae1930..7cef79050 100644 --- a/features/step_definitions/user_applications_steps.rb +++ b/features/step_definitions/user_applications_steps.rb @@ -14,9 +14,3 @@ end When /^I revoke the first authorization$/ do find(".app-revoke", match: :first).click end - -When /^An application manually registers$/ do - post api_openid_connect_authorizations_new_path, client_name: "", - redirect_uri: "http://example.org/", response_type: "id_token", scope: "openid", - state: 1234, display: "page", prompt: "none" -end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 9c07d9177..7527bacce 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -130,7 +130,6 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do end end end - context "when already authorized" do let!(:auth) { Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice, diff --git a/spec/controllers/api/openid_connect/user_applications_spec.rb b/spec/controllers/api/openid_connect/user_applications_spec.rb new file mode 100644 index 000000000..71c7dc7e3 --- /dev/null +++ b/spec/controllers/api/openid_connect/user_applications_spec.rb @@ -0,0 +1,17 @@ +require "spec_helper" + +describe Api::OpenidConnect::UserApplicationsController, type: :controller do + before do + @app = FactoryGirl.create(:o_auth_application_with_xss) + @user = FactoryGirl.create :user + FactoryGirl.create :auth_with_read, user: @user, o_auth_application: @app + sign_in :user, @user + end + + context "when try to XSS" do + it "should not include XSS script" do + get :index + expect(response.body).to_not include("") + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 7027fd57b..988a6b754 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -340,6 +340,11 @@ FactoryGirl.define do redirect_uris %w(http://localhost:3000/ http://localhost/) end + factory :o_auth_application_with_xss, class: Api::OpenidConnect::OAuthApplication do + client_name "" + redirect_uris %w(http://localhost:3000/) + end + factory :auth_with_read, class: Api::OpenidConnect::Authorization do o_auth_application user From 7b2be0d3c6154e91e713193a306f17cdd7c58c00 Mon Sep 17 00:00:00 2001 From: augier Date: Sat, 10 Oct 2015 14:41:16 +0200 Subject: [PATCH 074/105] Support displaying TOS and policy --- app/assets/stylesheets/user_applications.scss | 5 ++++ .../authorizations_controller.rb | 2 +- app/helpers/user_applications_helper.rb | 2 +- app/presenters/user_application_presenter.rb | 24 ++++++++++++++----- .../authorizations/_grants_list.haml | 14 +++++++++++ .../user_applications/_grants_list.haml | 14 +++++++++++ config/locales/diaspora/en.yml | 2 ++ db/schema.rb | 2 +- .../authorizations_controller_spec.rb | 10 ++++++++ 9 files changed, 66 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/user_applications.scss b/app/assets/stylesheets/user_applications.scss index 2b49bc5e4..39c94fc75 100644 --- a/app/assets/stylesheets/user_applications.scss +++ b/app/assets/stylesheets/user_applications.scss @@ -25,5 +25,10 @@ float: right; } +.application-tos-policy > b { + &:first-child { margin-right: 5px; } + &:nth-child(2) { margin-left: 5px; } +} + .user-consent { margin-top: 20px; } .approval-button { display: inline; } diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index e8369e372..0ad312708 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -189,7 +189,7 @@ module Api redirect_prompt_error_display(error, error_description) else render json: {error: "bad_request", - description: "No client with client_id " + params[:client_id] + " found"} + description: "No client with client_id #{params[:client_id]} found"} end else render json: {error: "bad_request", description: "Missing client id or redirect URI"} diff --git a/app/helpers/user_applications_helper.rb b/app/helpers/user_applications_helper.rb index 6fe2a7f57..190c43580 100644 --- a/app/helpers/user_applications_helper.rb +++ b/app/helpers/user_applications_helper.rb @@ -1,7 +1,7 @@ module UserApplicationsHelper def user_application_name(app) if app.name? - "#{app.name} (#{link_to(app.url, app.url)})" + "#{html_escape app.name} (#{link_to(app.url, app.url)})" else link_to(app.url, app.url) end diff --git a/app/presenters/user_application_presenter.rb b/app/presenters/user_application_presenter.rb index 8a0015622..7930d0808 100644 --- a/app/presenters/user_application_presenter.rb +++ b/app/presenters/user_application_presenter.rb @@ -14,19 +14,31 @@ class UserApplicationPresenter end def name - CGI::escape @app.client_name + @app.client_name end def image @app.image_uri end + def terms_of_services + @app.tos_uri + end + + def policy + @app.policy_uri + end + def name? - if @app.client_name - true - else - false - end + @app.client_name.present? + end + + def terms_of_services? + @app.tos_uri.present? + end + + def policy? + @app.policy_uri.present? end def url diff --git a/app/views/api/openid_connect/authorizations/_grants_list.haml b/app/views/api/openid_connect/authorizations/_grants_list.haml index 4f858457a..60cbbe4dd 100644 --- a/app/views/api/openid_connect/authorizations/_grants_list.haml +++ b/app/views/api/openid_connect/authorizations/_grants_list.haml @@ -15,3 +15,17 @@ - else .well = t("api.openid_connect.authorizations.new.no_requirement", name: user_application_name(app)).html_safe + + .small-horizontal-spacer + .application-tos-policy + - if app.terms_of_services? + %b= link_to t("api.openid_connect.user_applications.tos"), app.terms_of_services + + - if app.policy? && app.terms_of_services? + | + + - if app.policy? + %b= link_to t("api.openid_connect.user_applications.policy"), app.policy + + - if app.policy? || app.terms_of_services? + .small-horizontal-spacer diff --git a/app/views/api/openid_connect/user_applications/_grants_list.haml b/app/views/api/openid_connect/user_applications/_grants_list.haml index eb874053c..1ef5d12dc 100644 --- a/app/views/api/openid_connect/user_applications/_grants_list.haml +++ b/app/views/api/openid_connect/user_applications/_grants_list.haml @@ -14,3 +14,17 @@ - else .well = t("api.openid_connect.user_applications.index.no_requirement", name: user_application_name(app)).html_safe + + .small-horizontal-spacer + .application-tos-policy + - if app.terms_of_services? + %b= link_to t("api.openid_connect.user_applications.tos"), app.terms_of_services + + - if app.policy? && app.terms_of_services? + | + + - if app.policy? + %b= link_to t("api.openid_connect.user_applications.policy"), app.policy + + - if app.policy? || app.terms_of_services? + .small-horizontal-spacer diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 3539d69fd..48bc58a44 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -900,6 +900,8 @@ en: no_requirement: "%{name} requires no permissions" no_applications: "You have no authorized applications" revoke_autorization: "Revoke" + tos: "See the application's ToS" + policy: "See the application's privacy policy" scopes: openid: name: "basic profile" diff --git a/db/schema.rb b/db/schema.rb index a7232a417..d9e539bc8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150828132451) do +ActiveRecord::Schema.define(version: 20151003142048) do create_table "account_deletions", force: :cascade do |t| t.string "diaspora_handle", limit: 255 diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 7527bacce..a871996d3 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -2,6 +2,7 @@ require "spec_helper" describe Api::OpenidConnect::AuthorizationsController, type: :controller do let!(:client) { FactoryGirl.create(:o_auth_application) } + let!(:client_with_xss) { FactoryGirl.create(:o_auth_application_with_xss) } let!(:client_with_multiple_redirects) { FactoryGirl.create(:o_auth_application_with_multiple_redirects) } let!(:auth_with_read) { FactoryGirl.create(:auth_with_read) } @@ -129,7 +130,16 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do expect(json_body["error"]).to match("bad_request") end end + + context "when XSS script is passed as name" do + it "should escape html" do + post :new, client_id: client_with_xss.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) + expect(response.body).to_not include("") + end + end end + context "when already authorized" do let!(:auth) { Api::OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice, From d351db19827be71e7d0c3bfae36e96008350b6fb Mon Sep 17 00:00:00 2001 From: augier Date: Sun, 11 Oct 2015 18:24:15 +0200 Subject: [PATCH 075/105] Filter for prompt handling --- .../api/openid_connect/authorizations_controller.rb | 13 ++++++++++++- .../authorizations_controller_spec.rb | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 0ad312708..01dab9d24 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -6,7 +6,7 @@ module Api render json: {error: e.message || :error, status: e.status} end - before_action :authenticate_user! + before_action :auth_user_unless_prompt_none! def new auth = Api::OpenidConnect::Authorization.find_by_client_id_and_user(params[:client_id], current_user) @@ -201,6 +201,17 @@ module Api redirect_fragment = redirect_params_hash.compact.map {|key, value| key.to_s + "=" + value }.join("&") redirect_to params[:redirect_uri] + "?" + redirect_fragment end + + private + + def auth_user_unless_prompt_none! + if params[:prompt] == "none" && !user_signed_in? + render json: {error: "login_required", + description: "User must be first logged in when `prompt` is `none`"} + else + authenticate_user! + end + end end end end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index a871996d3..52476cb72 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -95,6 +95,19 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do end end + context "when prompt is none and user not signed in" do + before do + sign_out :user + end + + it "should return an interaction required error" do + post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" + json_body = JSON.parse(response.body) + expect(json_body["error"]).to match("login_required") + end + end + context "when prompt is none and consent" do it "should return an interaction required error" do post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", From adcf2ab7ab06b81e22e1c35677f27cbed53fa9f3 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Tue, 13 Oct 2015 17:29:20 -0700 Subject: [PATCH 076/105] Fix test for prompt == "none" --- app/controllers/api/openid_connect/authorizations_controller.rb | 2 -- .../api/openid_connect/authorizations_controller_spec.rb | 1 - 2 files changed, 3 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 01dab9d24..c42bbbbe0 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -202,8 +202,6 @@ module Api redirect_to params[:redirect_uri] + "?" + redirect_fragment end - private - def auth_user_unless_prompt_none! if params[:prompt] == "none" && !user_signed_in? render json: {error: "login_required", diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 52476cb72..a6b5d2a03 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -8,7 +8,6 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do before do sign_in :user, alice - allow(@controller).to receive(:current_user).and_return(alice) end describe "#new" do From e4edad0646e5bb520d05a2d801d42739b57b50d3 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Thu, 22 Oct 2015 01:34:58 -0700 Subject: [PATCH 077/105] Fix test for the auth missing the response_type parameter --- .../api/openid_connect/authorizations_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index c42bbbbe0..ee113fc10 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -3,7 +3,8 @@ module Api class AuthorizationsController < ApplicationController rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| logger.info e.backtrace[0, 10].join("\n") - render json: {error: e.message || :error, status: e.status} + error, description = e.message.split(" :: ") + handle_prompt_params_error(error, description) end before_action :auth_user_unless_prompt_none! From 82600003b39f47e57d7154f2a36ef7cabefeba9b Mon Sep 17 00:00:00 2001 From: theworldbright Date: Thu, 22 Oct 2015 20:34:40 -0700 Subject: [PATCH 078/105] Flash error messages when redirect_uri is invalid --- .../openid_connect/authorizations_controller.rb | 8 +++++--- config/locales/diaspora/en.yml | 2 ++ .../authorizations_controller_spec.rb | 17 ++++++++++------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index ee113fc10..9cf9399b6 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -189,11 +189,13 @@ module Api if app && app.redirect_uris.include?(params[:redirect_uri]) redirect_prompt_error_display(error, error_description) else - render json: {error: "bad_request", - description: "No client with client_id #{params[:client_id]} found"} + flash[:error] = I18n.t("api.openid_connect.authorizations.new.client_id_not_found", + client_id: params[:client_id], redirect_uri: params[:redirect_uri]) + redirect_to root_path end else - render json: {error: "bad_request", description: "Missing client id or redirect URI"} + flash[:error] = I18n.t("api.openid_connect.authorizations.new.bad_request") + redirect_to root_path end end diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 48bc58a44..62e8d9547 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -890,6 +890,8 @@ en: no_requirement: "%{name} requires no permissions" approve: "Approve" deny: "Deny" + bad_request: "Missing client id or redirect URI" + client_id_not_found: "No client with client_id %{client_id} with redirect URI %{redirect_uri} found" destroy: fail: "The attempt to revoke the authorization with ID %{id} has failed" user_applications: diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index a6b5d2a03..befcdb35b 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -35,7 +35,8 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an bad request error" do post :new, redirect_uri: "http://localhost:3000/", response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) - expect(response.body).to match("bad_request") + expect(response).to redirect_to root_path + expect(flash[:error]).to include("Missing client id") end end @@ -57,7 +58,8 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an invalid request error" do post :new, client_id: client_with_multiple_redirects.client_id, response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) - expect(response.body).to match("bad_request") + expect(response).to redirect_to root_path + expect(flash[:error]).to include("Missing client id or redirect URI") end end @@ -65,7 +67,8 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an invalid request error" do post :new, client_id: client.client_id, redirect_uri: "http://localhost:2000/", response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16) - expect(response.body).to match("bad_request") + expect(response).to redirect_to root_path + expect(flash[:error]).to include("No client") end end @@ -129,8 +132,8 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an account_selection_required error" do post :new, client_id: "random", redirect_uri: "http://localhost:3000/", response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" - json_body = JSON.parse(response.body) - expect(json_body["error"]).to match("bad_request") + expect(response).to redirect_to root_path + expect(flash[:error]).to include("No client") end end @@ -138,8 +141,8 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an account_selection_required error" do post :new, client_id: client.client_id, redirect_uri: "http://randomuri:3000/", response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" - json_body = JSON.parse(response.body) - expect(json_body["error"]).to match("bad_request") + expect(response).to redirect_to root_path + expect(flash[:error]).to include("No client") end end From 2a002d90c447f564e84c9a7cb581c27b3c123918 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Thu, 22 Oct 2015 23:25:57 -0700 Subject: [PATCH 079/105] Allow for longer redirect uri lists --- db/migrate/20150613202109_create_o_auth_applications.rb | 2 +- db/schema.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index f0fd5ca67..c40368622 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -6,7 +6,7 @@ class CreateOAuthApplications < ActiveRecord::Migration t.string :client_secret t.string :client_name - t.string :redirect_uris + t.text :redirect_uris t.string :response_types t.string :grant_types t.string :application_type, default: "web" diff --git a/db/schema.rb b/db/schema.rb index d9e539bc8..ef4f3daf3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -278,7 +278,7 @@ ActiveRecord::Schema.define(version: 20151003142048) do t.string "client_id", limit: 255 t.string "client_secret", limit: 255 t.string "client_name", limit: 255 - t.string "redirect_uris", limit: 255 + t.text "redirect_uris", limit: 65535 t.string "response_types", limit: 255 t.string "grant_types", limit: 255 t.string "application_type", limit: 255, default: "web" From 0fbcb7125552c07c0eb31c0b97c64f6fa480762c Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 23 Oct 2015 17:26:55 -0700 Subject: [PATCH 080/105] Add support for request_uri and claims --- .../authorizations_controller.rb | 21 +++++++---- .../openid_connect/discovery_controller.rb | 7 ++-- .../openid_connect/user_info_controller.rb | 8 ++++- .../api/openid_connect/authorization.rb | 2 +- app/serializers/user_info_serializer.rb | 6 +++- .../authorization_point/endpoint.rb | 14 ++++---- .../endpoint_confirmation_point.rb | 8 +++++ .../endpoint_start_point.rb | 26 ++++++++++++++ .../authorizations_controller_spec.rb | 35 +++++++++++++++++++ spec/factories.rb | 6 ++-- 10 files changed, 112 insertions(+), 21 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 9cf9399b6..4ee7d391d 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -76,10 +76,22 @@ module Api end def request_authorization_consent_form + add_claims_to_scopes endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user) handle_start_point_response(endpoint) end + def add_claims_to_scopes + return unless params[:claims] + claims_json = JSON.parse(params[:claims]) + return unless claims_json + claims_array = claims_json["userinfo"].try(:keys) + return unless claims_array + claims = claims_array.join(" ") + req = build_rack_request + req.update_param("scope", req[:scope] + " " + claims) + end + def logged_in_before?(seconds) if seconds.nil? false @@ -112,14 +124,12 @@ module Api end def save_params_and_render_consent_form(endpoint) - @o_auth_application, @response_type, @redirect_uri, @scopes, @request_object = *[ + @o_auth_application, @response_type, @redirect_uri, @scopes = *[ endpoint.o_auth_application, endpoint.response_type, - endpoint.redirect_uri, endpoint.scopes, endpoint.request_object + endpoint.redirect_uri, endpoint.scopes ] save_request_parameters - @app = UserApplicationPresenter.new @o_auth_application, @scopes - render :new end @@ -128,7 +138,6 @@ module Api session[:response_type] = @response_type session[:redirect_uri] = @redirect_uri session[:scopes] = scopes_as_space_seperated_values - session[:request_object] = @request_object session[:nonce] = params[:nonce] end @@ -153,7 +162,6 @@ module Api session.delete(:response_type) session.delete(:redirect_uri) session.delete(:scopes) - session.delete(:request_object) session.delete(:nonce) end @@ -167,7 +175,6 @@ module Api req.update_param("redirect_uri", session[:redirect_uri]) req.update_param("response_type", response_type_as_space_seperated_values) req.update_param("scope", session[:scopes]) - req.update_param("request_object", session[:request_object]) req.update_param("nonce", session[:nonce]) end diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index 5a12c6007..368d361c0 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -22,11 +22,14 @@ module Api jwks_uri: api_openid_connect_url, scopes_supported: Api::OpenidConnect::Authorization::SCOPES, response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types, - request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), + request_object_signing_alg_values_supported: %i(none), + request_parameter_supported: true, + request_uri_parameter_supported: true, subject_types_supported: %w(public pairwise), id_token_signing_alg_values_supported: %i(RS256), token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post private_key_jwt), - claims_supported: %w(sub nickname profile picture) + claims_parameter_supported: true, + claims_supported: %w(sub name nickname profile picture) ) end end diff --git a/app/controllers/api/openid_connect/user_info_controller.rb b/app/controllers/api/openid_connect/user_info_controller.rb index d5e7a8cf0..bcd639401 100644 --- a/app/controllers/api/openid_connect/user_info_controller.rb +++ b/app/controllers/api/openid_connect/user_info_controller.rb @@ -8,7 +8,13 @@ module Api end def show - render json: current_user, serializer: UserInfoSerializer, authorization: current_token.authorization + serializer = UserInfoSerializer.new(current_user) + auth = current_token.authorization + serializer.serialization_options = { authorization: auth } + attributes_without_essential = serializer.attributes.with_indifferent_access.select{|scope| auth.scopes.include? scope } + attributes = attributes_without_essential.merge( + sub: serializer.sub) + render json: attributes.to_json end def current_user diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index 87cc05661..cff556fa7 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -17,7 +17,7 @@ module Api scope :with_redirect_uri, ->(given_uri) { where redirect_uri: given_uri } - SCOPES = %w(openid read write) + SCOPES = %w(openid sub aud name nickname profile picture read write) def setup self.refresh_token = SecureRandom.hex(32) diff --git a/app/serializers/user_info_serializer.rb b/app/serializers/user_info_serializer.rb index 4fae962a3..7e7520693 100644 --- a/app/serializers/user_info_serializer.rb +++ b/app/serializers/user_info_serializer.rb @@ -1,11 +1,15 @@ class UserInfoSerializer < ActiveModel::Serializer - attributes :sub, :nickname, :profile, :picture + attributes :sub, :name, :nickname, :profile, :picture def sub auth = serialization_options[:authorization] Api::OpenidConnect::SubjectIdentifierCreator.createSub(auth) end + def name + (object.first_name || "") + (object.last_name || "") + end + def nickname object.name end diff --git a/lib/api/openid_connect/authorization_point/endpoint.rb b/lib/api/openid_connect/authorization_point/endpoint.rb index 680d494f8..8f3392bd9 100644 --- a/lib/api/openid_connect/authorization_point/endpoint.rb +++ b/lib/api/openid_connect/authorization_point/endpoint.rb @@ -3,12 +3,13 @@ module Api module AuthorizationPoint class Endpoint attr_accessor :app, :user, :o_auth_application, :redirect_uri, :response_type, - :scopes, :_request_, :request_uri, :request_object, :nonce + :scopes, :request_uri, :request_object, :nonce delegate :call, to: :app def initialize(user) @user = user @app = Rack::OAuth2::Server::Authorize.new do |req, res| + build_from_request_object(req) build_attributes(req, res) if OAuthApplication.available_response_types.include? Array(req.response_type).join(" ") handle_response_type(req, res) @@ -29,10 +30,6 @@ module Api raise NotImplementedError # Implemented by subclass end - def scopes - Api::OpenidConnect::Authorization::SCOPES - end - private def build_client(req) @@ -48,12 +45,17 @@ module Api end def build_scopes(req) + replace_profile_scope_with_specific_claims(req) @scopes = req.scope.map {|scope| scope.tap do |scope_name| - req.invalid_scope! "Unknown scope: #{scope_name}" unless scopes.include? scope_name + req.invalid_scope! "Unknown scope: #{scope_name}" unless auth_scopes.include? scope_name end } end + + def auth_scopes + Api::OpenidConnect::Authorization::SCOPES + end end end end diff --git a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb index dbb9abca5..c411b293b 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -19,6 +19,14 @@ module Api end end + def replace_profile_scope_with_specific_claims(req) + # Empty + end + + def build_from_request_object(req) + # Empty + end + private def approved!(req, res) diff --git a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb index 65cdaa1a3..3ce63cacf 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb @@ -2,9 +2,35 @@ module Api module OpenidConnect module AuthorizationPoint class EndpointStartPoint < Endpoint + def build_from_request_object(req) + request_object = build_request_object(req) + return unless request_object + claims = request_object.raw_attributes.with_indifferent_access[:claims].try(:[], :userinfo).try(:keys) + return unless claims + req.update_param("scope", req.scope + claims) + end + def handle_response_type(req, _res) @response_type = req.response_type end + + def replace_profile_scope_with_specific_claims(req) + profile_claims = %w(sub aud name nickname profile picture) + scopes_as_claims = req.scope.map { |scope| scope == "profile" ? profile_claims : [scope] }.flatten!.uniq + req.update_param("scope", scopes_as_claims) + end + + private + + def build_request_object(req) + if req.request_uri.present? + OpenIDConnect::RequestObject.fetch req.request_uri + elsif req.request.present? + OpenIDConnect::RequestObject.decode req.request + else + nil + end + end end end end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index befcdb35b..0e48a4404 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -22,6 +22,41 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do end end + context "using claims" do + it "should return a form page" do + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", claims: "{\"userinfo\": {\"name\": {\"essential\": true}}}", nonce: SecureRandom.hex(16), + state: SecureRandom.hex(16) + expect(response.body).to match("Diaspora Test Client") + end + end + + context "as a request object" do + it "should return a form page" do + header = JWT.encoded_header("none") + payload_hash = { client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: "hello", state: "hello", claims: { userinfo: { name: { essential: true } } } } + payload = JWT.encoded_payload(JSON.parse(payload_hash.to_json)) + request_object = header + "." + payload + "." + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: "hello", state: "hello", request: request_object + expect(response.body).to match("Diaspora Test Client") + end + end + + context "as a request object with no claims" do + it "should return a form page" do + header = JWT.encoded_header("none") + payload_hash = { client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", nonce: "hello", state: "hello" } + payload = JWT.encoded_payload(JSON.parse(payload_hash.to_json)) + request_object = header + "." + payload + "." + get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", + scope: "openid", nonce: "hello", state: "hello", request: request_object + expect(response.body).to match("Diaspora Test Client") + end + end + context "as POST request" do it "should return a form page" do post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", diff --git a/spec/factories.rb b/spec/factories.rb index 988a6b754..a7a5a8dca 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -348,19 +348,19 @@ FactoryGirl.define do factory :auth_with_read, class: Api::OpenidConnect::Authorization do o_auth_application user - scopes %w(openid read) + scopes %w(openid sub aud profile picture nickname name read) end factory :auth_with_read_and_ppid, class: Api::OpenidConnect::Authorization do association :o_auth_application, factory: :o_auth_application_with_ppid user - scopes %w(openid read) + scopes %w(openid sub aud profile picture nickname name read) end factory :auth_with_read_and_write, class: Api::OpenidConnect::Authorization do o_auth_application user - scopes %w(openid read write) + scopes %w(openid sub aud profile picture nickname name read write) end # Factories for the DiasporaFederation-gem From 8f5094c29e0c28ce74257bb2501ac54d21a875d7 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 23 Oct 2015 18:29:26 -0700 Subject: [PATCH 081/105] Gracefully handle SSL verification failure --- .../openid_connect/authorizations_controller.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 4ee7d391d..0a8459f2b 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -4,7 +4,12 @@ module Api rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| logger.info e.backtrace[0, 10].join("\n") error, description = e.message.split(" :: ") - handle_prompt_params_error(error, description) + handle_params_error(error, description) + end + + rescue_from OpenSSL::SSL::SSLError do |e| + logger.info e.backtrace[0, 10].join("\n") + handle_params_error("ssl_error", e.message) end before_action :auth_user_unless_prompt_none! @@ -49,7 +54,7 @@ module Api def handle_prompt(prompt, auth) if prompt.include? "select_account" - handle_prompt_params_error("account_selection_required", + handle_params_error("account_selection_required", "There is no support for choosing among multiple accounts") elsif prompt.include? "none" handle_prompt_none(prompt, auth) @@ -105,11 +110,11 @@ module Api if auth process_authorization_consent("true") else - handle_prompt_params_error("interaction_required", + handle_params_error("interaction_required", "The Authentication Request cannot be completed without end-user interaction") end else - handle_prompt_params_error("invalid_request", + handle_params_error("invalid_request", "The 'none' value cannot be used with any other prompt value") end end @@ -190,7 +195,7 @@ module Api end end - def handle_prompt_params_error(error, error_description) + def handle_params_error(error, error_description) if params[:client_id] && params[:redirect_uri] app = Api::OpenidConnect::OAuthApplication.find_by(client_id: params[:client_id]) if app && app.redirect_uris.include?(params[:redirect_uri]) From 2f8c391ac66457638c40734372983028eebd1866 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 23 Oct 2015 18:20:12 -0700 Subject: [PATCH 082/105] Fix pronto and travis errors --- .../authorizations_controller.rb | 22 +++--- .../openid_connect/user_info_controller.rb | 5 +- features/desktop/oidc_auth_code_flow.feature | 2 +- features/desktop/oidc_implicit_flow.feature | 2 +- features/step_definitions/auth_code_steps.rb | 2 +- .../step_definitions/implicit_flow_steps.rb | 4 +- .../endpoint_confirmation_point.rb | 4 +- .../endpoint_start_point.rb | 4 +- .../authorizations_controller_spec.rb | 13 +-- .../openid_connect/clients_controller_spec.rb | 79 ++++++++++--------- 10 files changed, 71 insertions(+), 66 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 0a8459f2b..2f1b60565 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -197,20 +197,24 @@ module Api def handle_params_error(error, error_description) if params[:client_id] && params[:redirect_uri] - app = Api::OpenidConnect::OAuthApplication.find_by(client_id: params[:client_id]) - if app && app.redirect_uris.include?(params[:redirect_uri]) - redirect_prompt_error_display(error, error_description) - else - flash[:error] = I18n.t("api.openid_connect.authorizations.new.client_id_not_found", - client_id: params[:client_id], redirect_uri: params[:redirect_uri]) - redirect_to root_path - end + handle_params_error_when_client_id_and_redirect_uri_exists(error, error_description) else flash[:error] = I18n.t("api.openid_connect.authorizations.new.bad_request") redirect_to root_path end end + def handle_params_error_when_client_id_and_redirect_uri_exists(error, error_description) + app = Api::OpenidConnect::OAuthApplication.find_by(client_id: params[:client_id]) + if app && app.redirect_uris.include?(params[:redirect_uri]) + redirect_prompt_error_display(error, error_description) + else + flash[:error] = I18n.t("api.openid_connect.authorizations.new.client_id_not_found", + client_id: params[:client_id], redirect_uri: params[:redirect_uri]) + redirect_to root_path + end + end + def redirect_prompt_error_display(error, error_description) redirect_params_hash = {error: error, error_description: error_description, state: params[:state]} redirect_fragment = redirect_params_hash.compact.map {|key, value| key.to_s + "=" + value }.join("&") @@ -219,7 +223,7 @@ module Api def auth_user_unless_prompt_none! if params[:prompt] == "none" && !user_signed_in? - render json: {error: "login_required", + render json: {error: "login_required", description: "User must be first logged in when `prompt` is `none`"} else authenticate_user! diff --git a/app/controllers/api/openid_connect/user_info_controller.rb b/app/controllers/api/openid_connect/user_info_controller.rb index bcd639401..666c2a48e 100644 --- a/app/controllers/api/openid_connect/user_info_controller.rb +++ b/app/controllers/api/openid_connect/user_info_controller.rb @@ -10,8 +10,9 @@ module Api def show serializer = UserInfoSerializer.new(current_user) auth = current_token.authorization - serializer.serialization_options = { authorization: auth } - attributes_without_essential = serializer.attributes.with_indifferent_access.select{|scope| auth.scopes.include? scope } + serializer.serialization_options = {authorization: auth} + attributes_without_essential = + serializer.attributes.with_indifferent_access.select {|scope| auth.scopes.include? scope } attributes = attributes_without_essential.merge( sub: serializer.sub) render json: attributes.to_json diff --git a/features/desktop/oidc_auth_code_flow.feature b/features/desktop/oidc_auth_code_flow.feature index 7a25bca72..c322ac484 100644 --- a/features/desktop/oidc_auth_code_flow.feature +++ b/features/desktop/oidc_auth_code_flow.feature @@ -7,7 +7,7 @@ Feature: Access protected resources using auth code flow When I register a new client And I send a post request from that client to the code flow authorization endpoint using a invalid client id And I sign in as "kent@kent.kent" - Then I should see an "bad_request" error + Then I should see a flash message containing "No client with" Scenario: Application is denied authorization When I register a new client diff --git a/features/desktop/oidc_implicit_flow.feature b/features/desktop/oidc_implicit_flow.feature index 15565ac20..abb2a2b91 100644 --- a/features/desktop/oidc_implicit_flow.feature +++ b/features/desktop/oidc_implicit_flow.feature @@ -7,7 +7,7 @@ Feature: Access protected resources using implicit flow When I register a new client And I send a post request from that client to the authorization endpoint using a invalid client id And I sign in as "kent@kent.kent" - Then I should see an "bad_request" error + Then I should see a flash message containing "No client with" Scenario: Application is denied authorization When I register a new client diff --git a/features/step_definitions/auth_code_steps.rb b/features/step_definitions/auth_code_steps.rb index bec5caa34..8b19f7f52 100644 --- a/features/step_definitions/auth_code_steps.rb +++ b/features/step_definitions/auth_code_steps.rb @@ -1,7 +1,7 @@ O_AUTH_QUERY_PARAMS_WITH_CODE = { redirect_uri: "http://localhost:3000", response_type: "code", - scope: "openid read", + scope: "openid profile read", nonce: "hello", state: "hi" } diff --git a/features/step_definitions/implicit_flow_steps.rb b/features/step_definitions/implicit_flow_steps.rb index bbbc0462a..f1603ec1a 100644 --- a/features/step_definitions/implicit_flow_steps.rb +++ b/features/step_definitions/implicit_flow_steps.rb @@ -1,7 +1,7 @@ O_AUTH_QUERY_PARAMS = { redirect_uri: "http://localhost:3000", response_type: "id_token token", - scope: "openid read", + scope: "openid profile read", nonce: "hello", state: "hi", prompt: "login" @@ -10,7 +10,7 @@ O_AUTH_QUERY_PARAMS = { O_AUTH_QUERY_PARAMS_WITH_MAX_AGE = { redirect_uri: "http://localhost:3000", response_type: "id_token token", - scope: "openid read", + scope: "openid profile read", nonce: "hello", state: "hi", prompt: "login", diff --git a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb index c411b293b..08e99b25d 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -19,11 +19,11 @@ module Api end end - def replace_profile_scope_with_specific_claims(req) + def replace_profile_scope_with_specific_claims(_req) # Empty end - def build_from_request_object(req) + def build_from_request_object(_req) # Empty end diff --git a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb index 3ce63cacf..87fd005fc 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb @@ -16,7 +16,7 @@ module Api def replace_profile_scope_with_specific_claims(req) profile_claims = %w(sub aud name nickname profile picture) - scopes_as_claims = req.scope.map { |scope| scope == "profile" ? profile_claims : [scope] }.flatten!.uniq + scopes_as_claims = req.scope.map {|scope| scope == "profile" ? profile_claims : [scope] }.flatten!.uniq req.update_param("scope", scopes_as_claims) end @@ -27,8 +27,6 @@ module Api OpenIDConnect::RequestObject.fetch req.request_uri elsif req.request.present? OpenIDConnect::RequestObject.decode req.request - else - nil end end end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 0e48a4404..3014b9309 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -25,8 +25,8 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do context "using claims" do it "should return a form page" do get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", - scope: "openid", claims: "{\"userinfo\": {\"name\": {\"essential\": true}}}", nonce: SecureRandom.hex(16), - state: SecureRandom.hex(16) + scope: "openid", claims: "{\"userinfo\": {\"name\": {\"essential\": true}}}", + nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) expect(response.body).to match("Diaspora Test Client") end end @@ -34,8 +34,9 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do context "as a request object" do it "should return a form page" do header = JWT.encoded_header("none") - payload_hash = { client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", - scope: "openid", nonce: "hello", state: "hello", claims: { userinfo: { name: { essential: true } } } } + payload_hash = {client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", nonce: "hello", state: "hello", + claims: {userinfo: {name: {essential: true}}}} payload = JWT.encoded_payload(JSON.parse(payload_hash.to_json)) request_object = header + "." + payload + "." get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", @@ -47,8 +48,8 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do context "as a request object with no claims" do it "should return a form page" do header = JWT.encoded_header("none") - payload_hash = { client_id: client.client_id, redirect_uri: "http://localhost:3000/", - response_type: "id_token", scope: "openid", nonce: "hello", state: "hello" } + payload_hash = {client_id: client.client_id, redirect_uri: "http://localhost:3000/", + response_type: "id_token", scope: "openid", nonce: "hello", state: "hello"} payload = JWT.encoded_payload(JSON.parse(payload_hash.to_json)) request_object = header + "." + payload + "." get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index d124ff042..a67f7f61f 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -5,8 +5,8 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters are passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "Host" => "example.com", "User-Agent" => "Ruby"}) + .with(headers: {:Accept => "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + :Host => "example.com", :"User-Agent" => "Ruby"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -22,8 +22,8 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters with jwks is passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "Host" => "example.com", "User-Agent" => "Ruby"}) + .with(headers: {:Accept => "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + :Host => "example.com", :"User-Agent" => "Ruby"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -31,42 +31,43 @@ describe Api::OpenidConnect::ClientsController, type: :controller do policy_uri: "http://example.com/policy", tos_uri: "http://example.com/tos", sector_identifier_uri: "http://example.com/uris", subject_type: "pairwise", token_endpoint_auth_method: "private_key_jwt", - "jwks": { - "keys": + jwks: { + keys: [ { - "use": "enc", - "e": "AQAB", - "d": "-lTBWkI-----lvCO6tuiDsR4qgJnUwnndQFwEI_4mLmD3iNWXrc8N--5Cjq55eLtuJjtvuQ", - "n": "--zYRQNDvIVsBDLQQIgrbctuGqj6lrXb31Jj3JIEYqH_4h5X9d0Q", - "q": "1q-r----pFtyTz_JksYYaotc_Z3Zy-Szw6a39IDbuYGy1qL-15oQuc", - "p": "-BfRjdgYouy4c6xAnGDgSMTip1YnPRyvbMaoYT9E_tEcBW5wOeoc", - "kid": "a0", - "kty": "RSA" - }, - {"use": "sig", - "e": "AQAB", - "d": "--x-gW---LRPowKrdvTuTo2p--HMI0pIEeFs7H_u5OW3jihjvoFClGPynHQhgWmQzlQRvWRXh6FhDVqFeGQ", - "n": "---TyeadDqQPWgbqX69UzcGq5irhzN8cpZ_JaTk3Y_uV6owanTZLVvCgdjaAnMYeZhb0KFw", - "q": "5E5XKK5njT--Hx3nF5sne5fleVfU-sZy6Za4B2U75PcE62oZgCPauOTAEm9Xuvrt5aMMovyzR8ecJZhm9bw7naU", - "p": "-BUGA-", - "kid": "a1", - "kty": "RSA"}, - { - "use": "sig", - "crv": "P-256", - "kty": "EC", - "y": "Yg4IRzHBMIsuQK2Oz0Uukp1aNDnpdoyk6QBMtmfGHQQ", - "x": "L0WUeVlc9r6YJd6ie9duvOU1RHwxSkJKA37IK9B4Bpc", - "kid": "a2" + use: "enc", + e: "AQAB", + d: "-lTBWkI-----lvCO6tuiDsR4qgJnUwnndQFwEI_4mLmD3iNWXrc8N--5Cjq55eLtuJjtvuQ", + n: "--zYRQNDvIVsBDLQQIgrbctuGqj6lrXb31Jj3JIEYqH_4h5X9d0Q", + q: "1q-r----pFtyTz_JksYYaotc_Z3Zy-Szw6a39IDbuYGy1qL-15oQuc", + p: "-BfRjdgYouy4c6xAnGDgSMTip1YnPRyvbMaoYT9E_tEcBW5wOeoc", + kid: "a0", + kty: "RSA" }, { - "use": "enc", - "crv": "P-256", - "kty": "EC", - "y": "E6E6g5_ziIZvfdAoACctnwOhuQYMvQzA259aftPn59M", - "x": "Yu8_BQE2L0f1MqnK0GumZOaj_77Tx70-LoudyRUnLM4", - "kid": "a3" + use: "sig", + e: "AQAB", + d: "--x-gW---LRPowKrdvTuTo2p--HMI0pIEeFs7H_u5OW3jihjvoFClGPynHQhgWmQzlQRvWRXh6FhDVqFeGQ", + n: "---TyeadDqQPWgbqX69UzcGq5irhzN8cpZ_JaTk3Y_uV6owanTZLVvCgdjaAnMYeZhb0KFw", + q: "5E5XKK5njT--Hx3nF5sne5fleVfU-sZy6Za4B2U75PcE62oZgCPauOTAEm9Xuvrt5aMMovyzR8ecJZhm9bw7naU", + p: "-BUGA-", + kid: "a1", + kty: "RSA"}, + { + use: "sig", + crv: "P-256", + kty: "EC", + y: "Yg4IRzHBMIsuQK2Oz0Uukp1aNDnpdoyk6QBMtmfGHQQ", + x: "L0WUeVlc9r6YJd6ie9duvOU1RHwxSkJKA37IK9B4Bpc", + kid: "a2" + }, + { + use: "enc", + crv: "P-256", + kty: "EC", + y: "E6E6g5_ziIZvfdAoACctnwOhuQYMvQzA259aftPn59M", + x: "Yu8_BQE2L0f1MqnK0GumZOaj_77Tx70-LoudyRUnLM4", + kid: "a3" } ] } @@ -80,11 +81,11 @@ describe Api::OpenidConnect::ClientsController, type: :controller do it "should return a client id" do stub_request(:get, "http://example.com/uris") .with(headers: {:Accept => "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "Host" => "example.com", :"User-Agent" => "Ruby"}) + :Host => "example.com", :"User-Agent" => "Ruby"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) stub_request(:get, "https://kentshikama.com/api/openid_connect/jwks.json") - .with(headers: {"Accept": "*/*", "Accept-Encoding": "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "Host": "kentshikama.com", "User-Agent": "Ruby"}) + .with(headers: {:Accept => "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + :Host => "kentshikama.com", :"User-Agent" => "Ruby"}) .to_return(status: 200, body: "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"qpW\",\"use\":\"sig\"}]}", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", From d028b5672e07a7724e9b11fdaa500518966f6c5f Mon Sep 17 00:00:00 2001 From: augier Date: Sat, 24 Oct 2015 16:15:33 -0700 Subject: [PATCH 083/105] Fix remarks --- .../authorizations_controller.rb | 28 ++++++++----------- .../api/openid_connect/clients_controller.rb | 4 +-- .../api/openid_connect/authorization.rb | 12 ++++---- app/models/api/openid_connect/id_token.rb | 2 +- .../api/openid_connect/o_auth_application.rb | 4 +-- app/presenters/user_application_presenter.rb | 3 +- app/serializers/user_info_serializer.rb | 2 +- .../endpoint_start_point.rb | 2 +- .../invalid_redirect_uri.rb | 2 +- .../invalid_sector_identifier_uri.rb | 2 +- .../subject_identifier_creator.rb | 4 +-- 11 files changed, 30 insertions(+), 35 deletions(-) rename lib/api/openid_connect/{exception => error}/invalid_redirect_uri.rb (90%) rename lib/api/openid_connect/{exception => error}/invalid_sector_identifier_uri.rb (90%) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 2f1b60565..0c7eb78a2 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -55,7 +55,7 @@ module Api def handle_prompt(prompt, auth) if prompt.include? "select_account" handle_params_error("account_selection_required", - "There is no support for choosing among multiple accounts") + "There is no support for choosing among multiple accounts") elsif prompt.include? "none" handle_prompt_none(prompt, auth) elsif prompt.include?("login") && logged_in_before?(60) @@ -92,9 +92,9 @@ module Api return unless claims_json claims_array = claims_json["userinfo"].try(:keys) return unless claims_array - claims = claims_array.join(" ") req = build_rack_request - req.update_param("scope", req[:scope] + " " + claims) + claims = claims_array.unshift(req[:scope]).join(" ") + req.update_param("scope", claims) end def logged_in_before?(seconds) @@ -111,16 +111,16 @@ module Api process_authorization_consent("true") else handle_params_error("interaction_required", - "The Authentication Request cannot be completed without end-user interaction") + "The Authentication Request cannot be completed without end-user interaction") end else handle_params_error("invalid_request", - "The 'none' value cannot be used with any other prompt value") + "The 'none' value cannot be used with any other prompt value") end end def handle_start_point_response(endpoint) - _status, header, response = *endpoint.call(request.env) + _status, header, response = endpoint.call(request.env) if response.redirect? redirect_to header["Location"] else @@ -129,10 +129,10 @@ module Api end def save_params_and_render_consent_form(endpoint) - @o_auth_application, @response_type, @redirect_uri, @scopes = *[ - endpoint.o_auth_application, endpoint.response_type, - endpoint.redirect_uri, endpoint.scopes - ] + @o_auth_application = endpoint.o_auth_application + @response_type = endpoint.response_type + @redirect_uri = endpoint.redirect_uri + @scopes = endpoint.scopes save_request_parameters @app = UserApplicationPresenter.new @o_auth_application, @scopes render :new @@ -157,7 +157,7 @@ module Api end def handle_confirmation_endpoint_response(endpoint) - _status, header, _response = *endpoint.call(request.env) + _status, header, _response = endpoint.call(request.env) delete_authorization_session_variables redirect_to header["Location"] end @@ -188,11 +188,7 @@ module Api end def response_type_as_space_seperated_values - if session[:response_type].respond_to?(:map) - session[:response_type].join(" ") - else - session[:response_type] - end + [*session[:response_type]].join(" ") end def handle_params_error(error, error_description) diff --git a/app/controllers/api/openid_connect/clients_controller.rb b/app/controllers/api/openid_connect/clients_controller.rb index d1f56dabe..38eabc92c 100644 --- a/app/controllers/api/openid_connect/clients_controller.rb +++ b/app/controllers/api/openid_connect/clients_controller.rb @@ -6,11 +6,11 @@ module Api end rescue_from OpenIDConnect::ValidationFailed, - ActiveRecord::RecordInvalid, Api::OpenidConnect::Exception::InvalidSectorIdentifierUri do |e| + ActiveRecord::RecordInvalid, Api::OpenidConnect::Error::InvalidSectorIdentifierUri do |e| validation_fail_as_json(e) end - rescue_from Api::OpenidConnect::Exception::InvalidRedirectUri do |e| + rescue_from Api::OpenidConnect::Error::InvalidRedirectUri do |e| validation_fail_redirect_uri(e) end diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index cff556fa7..ea73cfb79 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -4,9 +4,8 @@ module Api belongs_to :user belongs_to :o_auth_application - validates :user, presence: true + validates :user, presence: true, uniqueness: {scope: :o_auth_application} validates :o_auth_application, presence: true - validates :user, uniqueness: {scope: :o_auth_application} validate :validate_scope_names serialize :scopes, JSON @@ -38,8 +37,7 @@ module Api def create_code SecureRandom.hex(32).tap do |code| - self.code = code - save + update!(code: code) end end @@ -52,13 +50,13 @@ module Api end def self.find_by_client_id_and_user(client_id, user) - app = Api::OpenidConnect::OAuthApplication.find_by(client_id: client_id) + app = Api::OpenidConnect::OAuthApplication.where(client_id: client_id) find_by(o_auth_application: app, user: user) end def self.find_by_refresh_token(client_id, refresh_token) - Api::OpenidConnect::Authorization.joins(:o_auth_application).find_by( - o_auth_applications: {client_id: client_id}, refresh_token: refresh_token) + app = Api::OpenidConnect::OAuthApplication.where(client_id: client_id) + find_by(o_auth_application: app, refresh_token: refresh_token) end def self.use_code(code) diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb index b6d330376..92a0244c8 100644 --- a/app/models/api/openid_connect/id_token.rb +++ b/app/models/api/openid_connect/id_token.rb @@ -39,7 +39,7 @@ module Api end def build_sub - Api::OpenidConnect::SubjectIdentifierCreator.createSub(authorization) + Api::OpenidConnect::SubjectIdentifierCreator.create(authorization) end end end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 0004a93ad..e42b7fb51 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -56,7 +56,7 @@ module Api redirect_uris = attributes[:redirect_uris] sector_identifier_uri_includes_redirect_uris = (redirect_uris - sector_identifier_uri_json).empty? return if sector_identifier_uri_includes_redirect_uris - raise Api::OpenidConnect::Exception::InvalidSectorIdentifierUri.new + raise Api::OpenidConnect::Error::InvalidSectorIdentifierUri.new end def check_redirect_uris(attributes) @@ -64,7 +64,7 @@ module Api uri_array = redirect_uris.map {|uri| URI(uri) } any_uri_contains_fragment = uri_array.any? {|uri| !uri.fragment.nil? } return unless any_uri_contains_fragment - raise Api::OpenidConnect::Exception::InvalidRedirectUri.new + raise Api::OpenidConnect::Error::InvalidRedirectUri.new end def supported_metadata diff --git a/app/presenters/user_application_presenter.rb b/app/presenters/user_application_presenter.rb index 7930d0808..ad147626a 100644 --- a/app/presenters/user_application_presenter.rb +++ b/app/presenters/user_application_presenter.rb @@ -43,6 +43,7 @@ class UserApplicationPresenter def url client_redirect = URI(@app.redirect_uris[0]) - "#{client_redirect.scheme}://#{client_redirect.host}" + client_redirect.path = "/" + client_redirect.to_s end end diff --git a/app/serializers/user_info_serializer.rb b/app/serializers/user_info_serializer.rb index 7e7520693..4ef29f3b7 100644 --- a/app/serializers/user_info_serializer.rb +++ b/app/serializers/user_info_serializer.rb @@ -3,7 +3,7 @@ class UserInfoSerializer < ActiveModel::Serializer def sub auth = serialization_options[:authorization] - Api::OpenidConnect::SubjectIdentifierCreator.createSub(auth) + Api::OpenidConnect::SubjectIdentifierCreator.create(auth) end def name diff --git a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb index 87fd005fc..007a2c592 100644 --- a/lib/api/openid_connect/authorization_point/endpoint_start_point.rb +++ b/lib/api/openid_connect/authorization_point/endpoint_start_point.rb @@ -16,7 +16,7 @@ module Api def replace_profile_scope_with_specific_claims(req) profile_claims = %w(sub aud name nickname profile picture) - scopes_as_claims = req.scope.map {|scope| scope == "profile" ? profile_claims : [scope] }.flatten!.uniq + scopes_as_claims = req.scope.flat_map {|scope| scope == "profile" ? profile_claims : [scope] }.uniq req.update_param("scope", scopes_as_claims) end diff --git a/lib/api/openid_connect/exception/invalid_redirect_uri.rb b/lib/api/openid_connect/error/invalid_redirect_uri.rb similarity index 90% rename from lib/api/openid_connect/exception/invalid_redirect_uri.rb rename to lib/api/openid_connect/error/invalid_redirect_uri.rb index a407505b4..2cb5e3894 100644 --- a/lib/api/openid_connect/exception/invalid_redirect_uri.rb +++ b/lib/api/openid_connect/error/invalid_redirect_uri.rb @@ -1,6 +1,6 @@ module Api module OpenidConnect - module Exception + module Error class InvalidRedirectUri < ::ArgumentError def initialize super "Redirect uri contains fragment" diff --git a/lib/api/openid_connect/exception/invalid_sector_identifier_uri.rb b/lib/api/openid_connect/error/invalid_sector_identifier_uri.rb similarity index 90% rename from lib/api/openid_connect/exception/invalid_sector_identifier_uri.rb rename to lib/api/openid_connect/error/invalid_sector_identifier_uri.rb index 6383aee22..2431e1445 100644 --- a/lib/api/openid_connect/exception/invalid_sector_identifier_uri.rb +++ b/lib/api/openid_connect/error/invalid_sector_identifier_uri.rb @@ -1,6 +1,6 @@ module Api module OpenidConnect - module Exception + module Error class InvalidSectorIdentifierUri < ::ArgumentError def initialize super "Invalid sector identifier uri" diff --git a/lib/api/openid_connect/subject_identifier_creator.rb b/lib/api/openid_connect/subject_identifier_creator.rb index 3c39e93a1..b6a771fa0 100644 --- a/lib/api/openid_connect/subject_identifier_creator.rb +++ b/lib/api/openid_connect/subject_identifier_creator.rb @@ -1,7 +1,7 @@ module Api module OpenidConnect - class SubjectIdentifierCreator - def self.createSub(auth) + module SubjectIdentifierCreator + def self.create(auth) if auth.o_auth_application.ppid? identifier = auth.o_auth_application.sector_identifier_uri || URI.parse(auth.o_auth_application.redirect_uris[0]).host From f1b394de0f530f15217049dead853e27524d3089 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sat, 24 Oct 2015 17:19:22 -0700 Subject: [PATCH 084/105] Fix remaining remarks --- .../openid_connect/authorizations_controller.rb | 2 +- .../openid_connect/token_endpoint_controller.rb | 3 +-- app/models/api/openid_connect/authorization.rb | 3 +-- .../api/openid_connect/o_auth_application.rb | 6 ++---- ...invalid_sector_identifier_uri.rb => error.rb} | 5 +++++ .../openid_connect/error/invalid_redirect_uri.rb | 11 ----------- .../openid_connect/clients_controller_spec.rb | 16 ++++++++-------- 7 files changed, 18 insertions(+), 28 deletions(-) rename lib/api/openid_connect/{error/invalid_sector_identifier_uri.rb => error.rb} (60%) delete mode 100644 lib/api/openid_connect/error/invalid_redirect_uri.rb diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 0c7eb78a2..08e3c110f 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -9,7 +9,7 @@ module Api rescue_from OpenSSL::SSL::SSLError do |e| logger.info e.backtrace[0, 10].join("\n") - handle_params_error("ssl_error", e.message) + handle_params_error("bad_request", e.message) end before_action :auth_user_unless_prompt_none! diff --git a/app/controllers/api/openid_connect/token_endpoint_controller.rb b/app/controllers/api/openid_connect/token_endpoint_controller.rb index cfa06638f..c8ebf5b64 100644 --- a/app/controllers/api/openid_connect/token_endpoint_controller.rb +++ b/app/controllers/api/openid_connect/token_endpoint_controller.rb @@ -26,8 +26,7 @@ module Api def fetch_public_key(o_auth_app, jwt) public_key = fetch_public_key_from_json(o_auth_app.jwks, jwt) if public_key.empty? && o_auth_app.jwks_uri - uri = URI.parse(o_auth_app.jwks_uri) - response = Net::HTTP.get_response(uri) + response = Faraday.get(o_auth_app.jwks_uri) public_key = fetch_public_key_from_json(response.body, jwt) end raise Rack::OAuth2::Server::Authorize::BadRequest(:unauthorized_client) if public_key.empty? diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index ea73cfb79..dfb6a3e9a 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -67,8 +67,7 @@ module Api auth.destroy nil else - auth.code_used = true - auth.save + auth.update!(code_used: true) auth end end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index e42b7fb51..57d5fe27f 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -50,8 +50,7 @@ module Api def check_sector_identifier_uri(attributes) sector_identifier_uri = attributes[:sector_identifier_uri] return unless sector_identifier_uri - uri = URI.parse(sector_identifier_uri) - response = Net::HTTP.get_response(uri) + response = Faraday.get(sector_identifier_uri) sector_identifier_uri_json = JSON.parse(response.body) redirect_uris = attributes[:redirect_uris] sector_identifier_uri_includes_redirect_uris = (redirect_uris - sector_identifier_uri_json).empty? @@ -80,8 +79,7 @@ module Api if key == :subject_type attr[:ppid] = (value == "pairwise") elsif key == :jwks_uri - uri = URI.parse(value) - response = Net::HTTP.get_response(uri) + response = Faraday.get(value) attr[:jwks] = response.body attr[:jwks_uri] = value elsif key == :jwks diff --git a/lib/api/openid_connect/error/invalid_sector_identifier_uri.rb b/lib/api/openid_connect/error.rb similarity index 60% rename from lib/api/openid_connect/error/invalid_sector_identifier_uri.rb rename to lib/api/openid_connect/error.rb index 2431e1445..56059ad59 100644 --- a/lib/api/openid_connect/error/invalid_sector_identifier_uri.rb +++ b/lib/api/openid_connect/error.rb @@ -1,6 +1,11 @@ module Api module OpenidConnect module Error + class InvalidRedirectUri < ::ArgumentError + def initialize + super "Redirect uri contains fragment" + end + end class InvalidSectorIdentifierUri < ::ArgumentError def initialize super "Invalid sector identifier uri" diff --git a/lib/api/openid_connect/error/invalid_redirect_uri.rb b/lib/api/openid_connect/error/invalid_redirect_uri.rb deleted file mode 100644 index 2cb5e3894..000000000 --- a/lib/api/openid_connect/error/invalid_redirect_uri.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Api - module OpenidConnect - module Error - class InvalidRedirectUri < ::ArgumentError - def initialize - super "Redirect uri contains fragment" - end - end - end - end -end diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index a67f7f61f..1849f2c8a 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -5,8 +5,8 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters are passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {:Accept => "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - :Host => "example.com", :"User-Agent" => "Ruby"}) + .with(headers: {Accept: "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + :"User-Agent" => "Faraday v0.9.1"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -22,8 +22,8 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters with jwks is passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {:Accept => "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - :Host => "example.com", :"User-Agent" => "Ruby"}) + .with(headers: {Accept: "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + :"User-Agent" => "Faraday v0.9.1"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -80,12 +80,12 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters with jwks_uri is passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {:Accept => "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - :Host => "example.com", :"User-Agent" => "Ruby"}) + .with(headers: {Accept: "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + :"User-Agent" => "Faraday v0.9.1"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) stub_request(:get, "https://kentshikama.com/api/openid_connect/jwks.json") - .with(headers: {:Accept => "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - :Host => "kentshikama.com", :"User-Agent" => "Ruby"}) + .with(headers: {Accept: "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + :"User-Agent" => "Faraday v0.9.1"}) .to_return(status: 200, body: "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"qpW\",\"use\":\"sig\"}]}", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", From 3440709ec5567f73557bcffca6a14840b140d85d Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 6 Nov 2015 17:19:42 -0800 Subject: [PATCH 085/105] Explicitly state no support for user info alg --- app/controllers/api/openid_connect/discovery_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index 368d361c0..a2fe1b766 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -29,7 +29,8 @@ module Api id_token_signing_alg_values_supported: %i(RS256), token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post private_key_jwt), claims_parameter_supported: true, - claims_supported: %w(sub name nickname profile picture) + claims_supported: %w(sub name nickname profile picture), + userinfo_signing_alg_values_supported: %w(none) ) end end From 7865a30fecd34254fff2f517483a0c22fddf9b65 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 6 Nov 2015 17:20:27 -0800 Subject: [PATCH 086/105] Return an JSON error response for invalid jwks_uri --- app/controllers/api/openid_connect/clients_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/api/openid_connect/clients_controller.rb b/app/controllers/api/openid_connect/clients_controller.rb index 38eabc92c..66ef31cc6 100644 --- a/app/controllers/api/openid_connect/clients_controller.rb +++ b/app/controllers/api/openid_connect/clients_controller.rb @@ -14,6 +14,10 @@ module Api validation_fail_redirect_uri(e) end + rescue_from OpenSSL::SSL::SSLError do |e| + validation_fail_as_json(e) + end + def create registrar = OpenIDConnect::Client::Registrar.new(request.url, params) client = Api::OpenidConnect::OAuthApplication.register! registrar From 9fc8c63cae27615b4b097936531e9228e86ad342 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 6 Nov 2015 17:27:29 -0800 Subject: [PATCH 087/105] Fix hash styles for stub_request --- .../openid_connect/clients_controller_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index 1849f2c8a..50179d57f 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -5,8 +5,8 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters are passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {Accept: "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - :"User-Agent" => "Faraday v0.9.1"}) + .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "User-Agent" => "Faraday v0.9.1"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -22,8 +22,8 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters with jwks is passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {Accept: "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - :"User-Agent" => "Faraday v0.9.1"}) + .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "User-Agent" => "Faraday v0.9.1"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -80,12 +80,12 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters with jwks_uri is passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {Accept: "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - :"User-Agent" => "Faraday v0.9.1"}) + .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "User-Agent" => "Faraday v0.9.1"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) stub_request(:get, "https://kentshikama.com/api/openid_connect/jwks.json") - .with(headers: {Accept: "*/*", :"Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - :"User-Agent" => "Faraday v0.9.1"}) + .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "User-Agent" => "Faraday v0.9.1"}) .to_return(status: 200, body: "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"qpW\",\"use\":\"sig\"}]}", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", From c6bec2f2dcd6b98a6c6befb16222686bfe03a88a Mon Sep 17 00:00:00 2001 From: theworldbright Date: Fri, 6 Nov 2015 17:37:56 -0800 Subject: [PATCH 088/105] Return error to RP instead of user for prompt=none --- .../api/openid_connect/authorizations_controller.rb | 4 ++-- .../api/openid_connect/authorizations_controller_spec.rb | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 08e3c110f..fcab40d12 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -219,8 +219,8 @@ module Api def auth_user_unless_prompt_none! if params[:prompt] == "none" && !user_signed_in? - render json: {error: "login_required", - description: "User must be first logged in when `prompt` is `none`"} + handle_params_error("login_required", + "User must already be logged in when 'prompt' is 'none'") else authenticate_user! end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 3014b9309..ee60de7c2 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -141,8 +141,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an interaction required error" do post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" - json_body = JSON.parse(response.body) - expect(json_body["error"]).to match("login_required") + expect(response.location).to match("error=login_required") end end From 73c1f0bc709b142f3e9112176f69b7be90f7d891 Mon Sep 17 00:00:00 2001 From: augier Date: Sun, 8 Nov 2015 17:40:12 +0100 Subject: [PATCH 089/105] Fix pronto remarks --- app/assets/stylesheets/_application.scss | 5 ++--- app/assets/stylesheets/user_applications.scss | 9 +++++---- app/presenters/user_application_presenter.rb | 6 ++---- spec/spec_helper.rb | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index 514bcc153..a5b4f6e62 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -102,6 +102,5 @@ @import "blueimp-gallery"; @import "gallery"; -/* settings */ -@import "user_applications"; -@import "bootstrap3-switch"; \ No newline at end of file +// settings +@import 'user_applications'; diff --git a/app/assets/stylesheets/user_applications.scss b/app/assets/stylesheets/user_applications.scss index 39c94fc75..87a0005ce 100644 --- a/app/assets/stylesheets/user_applications.scss +++ b/app/assets/stylesheets/user_applications.scss @@ -1,9 +1,9 @@ .application-img { - margin: 9px 0; float: left; - width: 60px; + margin: 9px 0; max-height: 60px; text-align: center; + width: 60px; [class^="entypo-"] { font-size: 60px; @@ -11,6 +11,7 @@ margin: 0; padding: 0; width: 100%; + &::before { position: relative; top: -15px; @@ -19,10 +20,10 @@ } .application-authorizations { - width: calc(100% - 60px); - padding: 0 0 15px 15px; display: inline-block; float: right; + padding: 0 0 15px 15px; + width: calc(100% - 60px); } .application-tos-policy > b { diff --git a/app/presenters/user_application_presenter.rb b/app/presenters/user_application_presenter.rb index ad147626a..0bc2ea9d4 100644 --- a/app/presenters/user_application_presenter.rb +++ b/app/presenters/user_application_presenter.rb @@ -1,14 +1,12 @@ class UserApplicationPresenter + attr_reader :scopes + def initialize(application, scopes, authorization_id=nil) @app = application @scopes = scopes @authorization_id = authorization_id end - def scopes - @scopes - end - def id @authorization_id end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b97fb6166..6bad87360 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -79,7 +79,7 @@ end def client_assertion_with_nonexistent_client_id_path @client_assertion_with_nonexistent_client_id = File.join(File.dirname(__FILE__), "fixtures", - "client_assertion_with_nonexistent_client_id.txt") + "client_assertion_with_nonexistent_client_id.txt") end # Force fixture rebuild From 10938404e9518b943441b8af5c4412d18283a3ac Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sat, 21 Nov 2015 14:39:49 -0800 Subject: [PATCH 090/105] Fix HTTP request test mocks --- .../api/openid_connect/clients_controller_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index 50179d57f..19129bc49 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -6,7 +6,7 @@ describe Api::OpenidConnect::ClientsController, type: :controller do it "should return a client id" do stub_request(:get, "http://example.com/uris") .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "User-Agent" => "Faraday v0.9.1"}) + "User-Agent" => "Faraday v0.9.2"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -23,7 +23,7 @@ describe Api::OpenidConnect::ClientsController, type: :controller do it "should return a client id" do stub_request(:get, "http://example.com/uris") .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "User-Agent" => "Faraday v0.9.1"}) + "User-Agent" => "Faraday v0.9.2"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -81,11 +81,11 @@ describe Api::OpenidConnect::ClientsController, type: :controller do it "should return a client id" do stub_request(:get, "http://example.com/uris") .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "User-Agent" => "Faraday v0.9.1"}) + "User-Agent" => "Faraday v0.9.2"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) stub_request(:get, "https://kentshikama.com/api/openid_connect/jwks.json") .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "User-Agent" => "Faraday v0.9.1"}) + "User-Agent" => "Faraday v0.9.2"}) .to_return(status: 200, body: "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"qpW\",\"use\":\"sig\"}]}", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", From ebeafb7894697bf7fc682e5fca399706cb6e7dff Mon Sep 17 00:00:00 2001 From: augier Date: Wed, 11 Nov 2015 12:25:22 +0100 Subject: [PATCH 091/105] Add custom error page when prompt=none --- app/assets/stylesheets/_application.scss | 3 +++ .../stylesheets/openid_connect_error_page.scss | 7 +++++++ .../openid_connect/authorizations_controller.rb | 14 ++++++++++---- .../api/openid_connect/error/_error.html.haml | 6 ++++++ app/views/api/openid_connect/error/error.html.haml | 1 + .../api/openid_connect/error/error.mobile.haml | 1 + config/locales/diaspora/en.yml | 2 ++ 7 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 app/assets/stylesheets/openid_connect_error_page.scss create mode 100644 app/views/api/openid_connect/error/_error.html.haml create mode 100644 app/views/api/openid_connect/error/error.html.haml create mode 100644 app/views/api/openid_connect/error/error.mobile.haml diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index a5b4f6e62..d2c61e8dc 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -104,3 +104,6 @@ // settings @import 'user_applications'; + +// API +@import "openid_connect_error_page"; diff --git a/app/assets/stylesheets/openid_connect_error_page.scss b/app/assets/stylesheets/openid_connect_error_page.scss new file mode 100644 index 000000000..ccb566a5b --- /dev/null +++ b/app/assets/stylesheets/openid_connect_error_page.scss @@ -0,0 +1,7 @@ +.api-error { + margin-top: 20px; + box-shadow: $card-shadow; + background-color: $light-grey; + + h4 { text-align: center; } +} diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index fcab40d12..28f1a702c 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -110,8 +110,7 @@ module Api if auth process_authorization_consent("true") else - handle_params_error("interaction_required", - "The Authentication Request cannot be completed without end-user interaction") + render_error "The Authentication Request cannot be completed without end-user interaction" end else handle_params_error("invalid_request", @@ -219,12 +218,19 @@ module Api def auth_user_unless_prompt_none! if params[:prompt] == "none" && !user_signed_in? - handle_params_error("login_required", - "User must already be logged in when 'prompt' is 'none'") + render_error "User must be first logged in when `prompt` is `none`" + # render json: {error: "login_required", + # description: "User must be first logged in when `prompt` is `none`"} else authenticate_user! end end + + def render_error(error_description) + @error_description = error_description + render "api/openid_connect/error/error", + layout: request.format == :mobile ? "application" : "with_header_with_footer" + end end end end diff --git a/app/views/api/openid_connect/error/_error.html.haml b/app/views/api/openid_connect/error/_error.html.haml new file mode 100644 index 000000000..d9543bac4 --- /dev/null +++ b/app/views/api/openid_connect/error/_error.html.haml @@ -0,0 +1,6 @@ +.container-fluid + .row + .api-error.col-sm-6.col-sm-offset-3 + %h4 + %b= t("api.openid_connect.error_page.title") + %div= @error_description diff --git a/app/views/api/openid_connect/error/error.html.haml b/app/views/api/openid_connect/error/error.html.haml new file mode 100644 index 000000000..a9b15dbd0 --- /dev/null +++ b/app/views/api/openid_connect/error/error.html.haml @@ -0,0 +1 @@ += render partial: "api/openid_connect/error/error" diff --git a/app/views/api/openid_connect/error/error.mobile.haml b/app/views/api/openid_connect/error/error.mobile.haml new file mode 100644 index 000000000..a9b15dbd0 --- /dev/null +++ b/app/views/api/openid_connect/error/error.mobile.haml @@ -0,0 +1 @@ += render partial: "api/openid_connect/error/error" diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 62e8d9547..b6dee2b3f 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -917,6 +917,8 @@ en: write: name: "send posts, conversations and reactions" description: "This allows the application to send new posts, write conversations, and send reactions" + error_page: + title: "Oh! Something went wrong :(" people: zero: "No people" From ed1dc256a85641f24610e3de9bbd0c6026da82b1 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 22 Nov 2015 12:54:18 -0800 Subject: [PATCH 092/105] Fix handling of error message in authorization controller --- .../authorizations_controller.rb | 53 ++++++++++--------- .../api/openid_connect/error/_error.html.haml | 3 +- features/desktop/oidc_auth_code_flow.feature | 2 +- features/desktop/oidc_implicit_flow.feature | 2 +- .../step_definitions/oidc_common_steps.rb | 5 ++ .../authorizations_controller_spec.rb | 23 +++----- 6 files changed, 45 insertions(+), 43 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 28f1a702c..192606ee8 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -4,7 +4,7 @@ module Api rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| logger.info e.backtrace[0, 10].join("\n") error, description = e.message.split(" :: ") - handle_params_error(error, description) + handle_params_error(error, "The request was malformed: please double check the client id and redirect uri.") end rescue_from OpenSSL::SSL::SSLError do |e| @@ -56,8 +56,6 @@ module Api if prompt.include? "select_account" handle_params_error("account_selection_required", "There is no support for choosing among multiple accounts") - elsif prompt.include? "none" - handle_prompt_none(prompt, auth) elsif prompt.include?("login") && logged_in_before?(60) reauthenticate elsif prompt.include? "consent" @@ -105,19 +103,6 @@ module Api end end - def handle_prompt_none(prompt, auth) - if prompt == ["none"] - if auth - process_authorization_consent("true") - else - render_error "The Authentication Request cannot be completed without end-user interaction" - end - else - handle_params_error("invalid_request", - "The 'none' value cannot be used with any other prompt value") - end - end - def handle_start_point_response(endpoint) _status, header, response = endpoint.call(request.env) if response.redirect? @@ -194,8 +179,7 @@ module Api if params[:client_id] && params[:redirect_uri] handle_params_error_when_client_id_and_redirect_uri_exists(error, error_description) else - flash[:error] = I18n.t("api.openid_connect.authorizations.new.bad_request") - redirect_to root_path + render_error error_description end end @@ -204,9 +188,7 @@ module Api if app && app.redirect_uris.include?(params[:redirect_uri]) redirect_prompt_error_display(error, error_description) else - flash[:error] = I18n.t("api.openid_connect.authorizations.new.client_id_not_found", - client_id: params[:client_id], redirect_uri: params[:redirect_uri]) - redirect_to root_path + render_error "Invalid client id or redirect uri" end end @@ -217,15 +199,36 @@ module Api end def auth_user_unless_prompt_none! - if params[:prompt] == "none" && !user_signed_in? - render_error "User must be first logged in when `prompt` is `none`" - # render json: {error: "login_required", - # description: "User must be first logged in when `prompt` is `none`"} + prompt = params[:prompt] + if prompt && prompt.include?("none") + handle_prompt_none else authenticate_user! end end + def handle_prompt_none + if params[:prompt] == "none" + if user_signed_in? + client_id = params[:client_id] + if client_id + auth = Api::OpenidConnect::Authorization.find_by_client_id_and_user(client_id, current_user) + if auth + process_authorization_consent("true") + else + handle_params_error("interaction_required", "User must already be authorized when `prompt` is `none`") + end + else + handle_params_error("bad_request", "Client ID is missing from request") + end + else + handle_params_error("login_required", "User must already be logged in when `prompt` is `none`") + end + else + handle_params_error("invalid_request", "The 'none' value cannot be used with any other prompt value") + end + end + def render_error(error_description) @error_description = error_description render "api/openid_connect/error/error", diff --git a/app/views/api/openid_connect/error/_error.html.haml b/app/views/api/openid_connect/error/_error.html.haml index d9543bac4..78ff3f9f4 100644 --- a/app/views/api/openid_connect/error/_error.html.haml +++ b/app/views/api/openid_connect/error/_error.html.haml @@ -3,4 +3,5 @@ .api-error.col-sm-6.col-sm-offset-3 %h4 %b= t("api.openid_connect.error_page.title") - %div= @error_description + %div{id: "openid_connect_error_description"} + = @error_description diff --git a/features/desktop/oidc_auth_code_flow.feature b/features/desktop/oidc_auth_code_flow.feature index c322ac484..22460b7d1 100644 --- a/features/desktop/oidc_auth_code_flow.feature +++ b/features/desktop/oidc_auth_code_flow.feature @@ -7,7 +7,7 @@ Feature: Access protected resources using auth code flow When I register a new client And I send a post request from that client to the code flow authorization endpoint using a invalid client id And I sign in as "kent@kent.kent" - Then I should see a flash message containing "No client with" + Then I should see a message containing "Invalid client id or redirect uri" Scenario: Application is denied authorization When I register a new client diff --git a/features/desktop/oidc_implicit_flow.feature b/features/desktop/oidc_implicit_flow.feature index abb2a2b91..3e090a303 100644 --- a/features/desktop/oidc_implicit_flow.feature +++ b/features/desktop/oidc_implicit_flow.feature @@ -7,7 +7,7 @@ Feature: Access protected resources using implicit flow When I register a new client And I send a post request from that client to the authorization endpoint using a invalid client id And I sign in as "kent@kent.kent" - Then I should see a flash message containing "No client with" + Then I should see a message containing "Invalid client id or redirect uri" Scenario: Application is denied authorization When I register a new client diff --git a/features/step_definitions/oidc_common_steps.rb b/features/step_definitions/oidc_common_steps.rb index 5c562f891..d5f605b2d 100644 --- a/features/step_definitions/oidc_common_steps.rb +++ b/features/step_definitions/oidc_common_steps.rb @@ -33,3 +33,8 @@ Then /^I should receive an "([^\"]*)" error$/ do |error_message| user_info_json = JSON.parse(last_response.body) expect(user_info_json["error"]).to have_content(error_message) end + +Then(/^I should see a message containing "(.*?)"$/) do |message| + expect(find("#openid_connect_error_description").text).to eq(message) +end + diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index ee60de7c2..0c7e4ab0f 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -71,8 +71,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an bad request error" do post :new, redirect_uri: "http://localhost:3000/", response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) - expect(response).to redirect_to root_path - expect(flash[:error]).to include("Missing client id") + expect(response.body).to include("The request was malformed") end end @@ -94,17 +93,15 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an invalid request error" do post :new, client_id: client_with_multiple_redirects.client_id, response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16), state: SecureRandom.hex(16) - expect(response).to redirect_to root_path - expect(flash[:error]).to include("Missing client id or redirect URI") + expect(response.body).to include("The request was malformed") end end context "when redirect URI does not match pre-registered URIs" do - it "should return an invalid request error" do + it "should return an invalid request error", focus: true do post :new, client_id: client.client_id, redirect_uri: "http://localhost:2000/", response_type: "id_token", scope: "openid", nonce: SecureRandom.hex(16) - expect(response).to redirect_to root_path - expect(flash[:error]).to include("No client") + expect(response.body).to include("Invalid client id or redirect uri") end end @@ -128,8 +125,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an interaction required error" do post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" - expect(response.location).to match("error=interaction_required") - expect(response.location).to match("state=1234") + expect(response.body).to include("User must already be authorized when `prompt` is `none`") end end @@ -141,7 +137,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an interaction required error" do post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" - expect(response.location).to match("error=login_required") + expect(response.body).to include("User must already be logged in when `prompt` is `none`") end end @@ -150,7 +146,6 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do post :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none consent" expect(response.location).to match("error=invalid_request") - expect(response.location).to match("state=1234") end end @@ -167,8 +162,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an account_selection_required error" do post :new, client_id: "random", redirect_uri: "http://localhost:3000/", response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" - expect(response).to redirect_to root_path - expect(flash[:error]).to include("No client") + expect(response.body).to include("Invalid client id or redirect uri") end end @@ -176,8 +170,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "should return an account_selection_required error" do post :new, client_id: client.client_id, redirect_uri: "http://randomuri:3000/", response_type: "id_token", scope: "openid", state: 1234, display: "page", prompt: "none" - expect(response).to redirect_to root_path - expect(flash[:error]).to include("No client") + expect(response.body).to include("Invalid client id or redirect uri") end end From 773a5a67d96b8f3118b25a84524885374ce220e0 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 22 Nov 2015 13:36:28 -0800 Subject: [PATCH 093/105] Add default kid to ID token --- app/models/api/openid_connect/id_token.rb | 4 +++- spec/lib/api/openid_connect/token_endpoint_spec.rb | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb index 92a0244c8..4b8e83137 100644 --- a/app/models/api/openid_connect/id_token.rb +++ b/app/models/api/openid_connect/id_token.rb @@ -14,7 +14,9 @@ module Api end def to_jwt(options={}) - to_response_object(options).to_jwt OpenidConnect::IdTokenConfig::PRIVATE_KEY + to_response_object(options).to_jwt(OpenidConnect::IdTokenConfig::PRIVATE_KEY) do |jwt| + jwt.kid = :default + end end def to_response_object(options={}) diff --git a/spec/lib/api/openid_connect/token_endpoint_spec.rb b/spec/lib/api/openid_connect/token_endpoint_spec.rb index 6d944626f..37eba5380 100644 --- a/spec/lib/api/openid_connect/token_endpoint_spec.rb +++ b/spec/lib/api/openid_connect/token_endpoint_spec.rb @@ -36,6 +36,13 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do expect(decoded_token.exp).to be > Time.zone.now.utc.to_i end + it "should return an id token with a kid" do + json = JSON.parse(response.body) + encoded_id_token = json["id_token"] + kid = JSON::JWT.decode(encoded_id_token, :skip_verification).header[:kid] + expect(kid).to eq("default") + end + it "should return a valid access token" do json = JSON.parse(response.body) encoded_id_token = json["id_token"] From fd4022a55cd0c17b9780cdd256c99ad1524c2287 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 22 Nov 2015 14:15:13 -0800 Subject: [PATCH 094/105] Fix pronto remarks --- app/assets/stylesheets/_application.scss | 10 +++---- .../openid_connect_error_page.scss | 4 +-- .../authorizations_controller.rb | 28 +++++++++++-------- .../step_definitions/oidc_common_steps.rb | 1 - 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index d2c61e8dc..f61519547 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -1,7 +1,7 @@ @import 'perfect-scrollbar'; @import 'color-variables'; -@import "bootstrap-complete.scss"; +@import 'bootstrap-complete'; @import 'mixins'; @@ -99,11 +99,11 @@ @import 'statistics'; /* gallery */ -@import "blueimp-gallery"; -@import "gallery"; +@import 'blueimp-gallery'; +@import 'gallery'; // settings @import 'user_applications'; -// API -@import "openid_connect_error_page"; +// OpenID Connect (API) +@import 'openid_connect_error_page'; diff --git a/app/assets/stylesheets/openid_connect_error_page.scss b/app/assets/stylesheets/openid_connect_error_page.scss index ccb566a5b..15e659fc3 100644 --- a/app/assets/stylesheets/openid_connect_error_page.scss +++ b/app/assets/stylesheets/openid_connect_error_page.scss @@ -1,7 +1,7 @@ .api-error { - margin-top: 20px; - box-shadow: $card-shadow; background-color: $light-grey; + box-shadow: $card-shadow; + margin-top: 20px; h4 { text-align: center; } } diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 192606ee8..a430bdd01 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -3,7 +3,7 @@ module Api class AuthorizationsController < ApplicationController rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e| logger.info e.backtrace[0, 10].join("\n") - error, description = e.message.split(" :: ") + error, _description = e.message.split(" :: ") handle_params_error(error, "The request was malformed: please double check the client id and redirect uri.") end @@ -210,17 +210,7 @@ module Api def handle_prompt_none if params[:prompt] == "none" if user_signed_in? - client_id = params[:client_id] - if client_id - auth = Api::OpenidConnect::Authorization.find_by_client_id_and_user(client_id, current_user) - if auth - process_authorization_consent("true") - else - handle_params_error("interaction_required", "User must already be authorized when `prompt` is `none`") - end - else - handle_params_error("bad_request", "Client ID is missing from request") - end + handle_prompt_with_signed_in_user else handle_params_error("login_required", "User must already be logged in when `prompt` is `none`") end @@ -229,6 +219,20 @@ module Api end end + def handle_prompt_with_signed_in_user + client_id = params[:client_id] + if client_id + auth = Api::OpenidConnect::Authorization.find_by_client_id_and_user(client_id, current_user) + if auth + process_authorization_consent("true") + else + handle_params_error("interaction_required", "User must already be authorized when `prompt` is `none`") + end + else + handle_params_error("bad_request", "Client ID is missing from request") + end + end + def render_error(error_description) @error_description = error_description render "api/openid_connect/error/error", diff --git a/features/step_definitions/oidc_common_steps.rb b/features/step_definitions/oidc_common_steps.rb index d5f605b2d..743dfd8a7 100644 --- a/features/step_definitions/oidc_common_steps.rb +++ b/features/step_definitions/oidc_common_steps.rb @@ -37,4 +37,3 @@ end Then(/^I should see a message containing "(.*?)"$/) do |message| expect(find("#openid_connect_error_description").text).to eq(message) end - From 7e8bd0f41164c94f52d08f1ec216bf91a81bb1a8 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 22 Nov 2015 14:42:54 -0800 Subject: [PATCH 095/105] Add fallback for failed app logo rendering --- app/assets/javascripts/api/authorization_page.js | 5 +++++ app/assets/javascripts/main.js | 1 + .../api/openid_connect/authorizations/_grants_list.haml | 2 +- .../api/openid_connect/user_applications/_grants_list.haml | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/api/authorization_page.js diff --git a/app/assets/javascripts/api/authorization_page.js b/app/assets/javascripts/api/authorization_page.js new file mode 100644 index 000000000..d61b941ec --- /dev/null +++ b/app/assets/javascripts/api/authorization_page.js @@ -0,0 +1,5 @@ +$(document).ready(function() { + $("#js-app-logo").error(function () { + $(this).attr("src", ImagePaths.get("user/default.png")); + }); +}); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index ff8d91750..151d99bbf 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -45,3 +45,4 @@ //= require bootstrap-switch //= require blueimp-gallery //= require leaflet +//= require api/authorization_page diff --git a/app/views/api/openid_connect/authorizations/_grants_list.haml b/app/views/api/openid_connect/authorizations/_grants_list.haml index 60cbbe4dd..454087044 100644 --- a/app/views/api/openid_connect/authorizations/_grants_list.haml +++ b/app/views/api/openid_connect/authorizations/_grants_list.haml @@ -1,6 +1,6 @@ .application-img - if app.image - = image_tag app.image, class: "img-responsive" + = image_tag app.image, class: "img-responsive", id: "js-app-logo" - else %i.entypo-browser .application-authorizations diff --git a/app/views/api/openid_connect/user_applications/_grants_list.haml b/app/views/api/openid_connect/user_applications/_grants_list.haml index 1ef5d12dc..210a8a0fe 100644 --- a/app/views/api/openid_connect/user_applications/_grants_list.haml +++ b/app/views/api/openid_connect/user_applications/_grants_list.haml @@ -1,6 +1,6 @@ .application-img - if app.image - = image_tag app.image, class: "img-responsive" + = image_tag app.image, class: "img-responsive", id: "js-app-logo" - else %i.entypo-browser .application-authorizations From 1e3421713a7f103406c683ff6ddf864cec246060 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 22 Nov 2015 15:22:35 -0800 Subject: [PATCH 096/105] Handle error when request object is signed --- .../api/openid_connect/authorizations_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index a430bdd01..a088d73e2 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -12,6 +12,11 @@ module Api handle_params_error("bad_request", e.message) end + rescue_from JSON::JWS::VerificationFailed do |e| + logger.info e.backtrace[0, 10].join("\n") + handle_params_error("bad_request", e.message) + end + before_action :auth_user_unless_prompt_none! def new From a4095692b7a258ed15f3bf14b44d6f7fffe634ec Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 22 Nov 2015 15:54:05 -0800 Subject: [PATCH 097/105] Add default kid to jwks.json --- app/controllers/api/openid_connect/id_tokens_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/openid_connect/id_tokens_controller.rb b/app/controllers/api/openid_connect/id_tokens_controller.rb index 9e8c65b89..1528b2733 100644 --- a/app/controllers/api/openid_connect/id_tokens_controller.rb +++ b/app/controllers/api/openid_connect/id_tokens_controller.rb @@ -8,7 +8,7 @@ module Api private def build_jwk - JSON::JWK.new(Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY, use: :sig) + JSON::JWK.new(Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY, use: :sig, kid: :default) end end end From 9f85a90f55b82ccbfed33110cf82d11cdab79ab0 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 22 Nov 2015 16:04:08 -0800 Subject: [PATCH 098/105] Update code_used to false after issues new code --- app/models/api/openid_connect/authorization.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index dfb6a3e9a..fb8c006cc 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -38,6 +38,7 @@ module Api def create_code SecureRandom.hex(32).tap do |code| update!(code: code) + update!(code_used: false) end end From 4cde41486bfd22b87d88084433302ace3eaeb2c1 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 22 Nov 2015 16:24:04 -0800 Subject: [PATCH 099/105] Fix handling of prompt=login --- .../openid_connect/authorizations_controller.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index a088d73e2..7acddcfaf 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -23,7 +23,7 @@ module Api auth = Api::OpenidConnect::Authorization.find_by_client_id_and_user(params[:client_id], current_user) reset_auth(auth) if logged_in_before?(params[:max_age]) - reauthenticate + reauthenticate(params) elsif params[:prompt] prompt = params[:prompt].split(" ") handle_prompt(prompt, auth) @@ -61,8 +61,6 @@ module Api if prompt.include? "select_account" handle_params_error("account_selection_required", "There is no support for choosing among multiple accounts") - elsif prompt.include?("login") && logged_in_before?(60) - reauthenticate elsif prompt.include? "consent" request_authorization_consent_form else @@ -70,11 +68,6 @@ module Api end end - def reauthenticate - sign_out current_user - redirect_to new_api_openid_connect_authorization_path(params) - end - def handle_authorization_form(auth) if auth process_authorization_consent("true") @@ -207,6 +200,9 @@ module Api prompt = params[:prompt] if prompt && prompt.include?("none") handle_prompt_none + elsif prompt && prompt.include?("login") + new_params = params.merge!(prompt: prompt.remove("login")) + reauthenticate(new_params) else authenticate_user! end @@ -238,6 +234,11 @@ module Api end end + def reauthenticate(params) + sign_out current_user + redirect_to new_api_openid_connect_authorization_path(params) + end + def render_error(error_description) @error_description = error_description render "api/openid_connect/error/error", From c1e1f9bf69be770f5014678a2f28873ff4452000 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Sun, 22 Nov 2015 19:12:26 -0800 Subject: [PATCH 100/105] Fix 500 error when unknown algorithm is used for JWT --- .../api/openid_connect/token_endpoint_controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/openid_connect/token_endpoint_controller.rb b/app/controllers/api/openid_connect/token_endpoint_controller.rb index c8ebf5b64..9f145058d 100644 --- a/app/controllers/api/openid_connect/token_endpoint_controller.rb +++ b/app/controllers/api/openid_connect/token_endpoint_controller.rb @@ -46,16 +46,16 @@ module Api end end - rescue_from Rack::OAuth2::Server::Authorize::BadRequest, JSON::JWT::InvalidFormat do |e| + rescue_from Rack::OAuth2::Server::Authorize::BadRequest, + JSON::JWT::InvalidFormat, JSON::JWK::UnknownAlgorithm do |e| logger.info e.backtrace[0, 10].join("\n") - render json: {error: :invalid_request, error_description: e.message, status: e.status} - end - rescue_from JSON::JWT::InvalidFormat do |e| render json: {error: :invalid_request, error_description: e.message, status: 400} end rescue_from JSON::JWT::VerificationFailed do |e| + logger.info e.backtrace[0, 10].join("\n") render json: {error: :invalid_grant, error_description: e.message, status: 400} end + end end end From 10314ffc8c12260df7aaee22113e2d30eed9d3a5 Mon Sep 17 00:00:00 2001 From: augier Date: Mon, 23 Nov 2015 10:41:40 +0100 Subject: [PATCH 101/105] Fixing more remarks --- .../api/openid_connect/authorizations/_grants_list.haml | 6 +++--- config/initializers/cors.rb | 2 +- config/locales/diaspora/en.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/api/openid_connect/authorizations/_grants_list.haml b/app/views/api/openid_connect/authorizations/_grants_list.haml index 454087044..63d59211f 100644 --- a/app/views/api/openid_connect/authorizations/_grants_list.haml +++ b/app/views/api/openid_connect/authorizations/_grants_list.haml @@ -10,7 +10,7 @@ %ul - app.scopes.each do |scope| %li - %b= t("api.openid_connect.scopes.#{scope}.name") + %strong= t("api.openid_connect.scopes.#{scope}.name") %p= t("api.openid_connect.scopes.#{scope}.description") - else .well @@ -19,13 +19,13 @@ .small-horizontal-spacer .application-tos-policy - if app.terms_of_services? - %b= link_to t("api.openid_connect.user_applications.tos"), app.terms_of_services + %strong= link_to t("api.openid_connect.user_applications.tos"), app.terms_of_services - if app.policy? && app.terms_of_services? | - if app.policy? - %b= link_to t("api.openid_connect.user_applications.policy"), app.policy + %strong= link_to t("api.openid_connect.user_applications.policy"), app.policy - if app.policy? || app.terms_of_services? .small-horizontal-spacer diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 6ca623de5..9ad8a18a5 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -6,6 +6,6 @@ Rails.application.config.middleware.insert 0, Rack::Cors do resource "/.well-known/webfinger" resource "/.well-known/openid-configuration" resource "/api/openid_connect/user_info", methods: %i(get post) - resource "/api/v0/*", methods: %i(get post delete) + resource "/api/v0/*", methods: %i(delete get post) end end diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index b6dee2b3f..349c027df 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -893,7 +893,7 @@ en: bad_request: "Missing client id or redirect URI" client_id_not_found: "No client with client_id %{client_id} with redirect URI %{redirect_uri} found" destroy: - fail: "The attempt to revoke the authorization with ID %{id} has failed" + fail: "The attempt to revoke the authorization with ID %{id} failed" user_applications: index: edit_applications: "Applications" @@ -902,7 +902,7 @@ en: no_requirement: "%{name} requires no permissions" no_applications: "You have no authorized applications" revoke_autorization: "Revoke" - tos: "See the application's ToS" + tos: "See the application's terms of service" policy: "See the application's privacy policy" scopes: openid: From ef7ea1a855f6c8cf325a162e95a0186b3ec32087 Mon Sep 17 00:00:00 2001 From: augier Date: Mon, 23 Nov 2015 11:02:56 +0100 Subject: [PATCH 102/105] General text error + CSS styling Minor merge conflict fix by theworldbright --- app/assets/stylesheets/mobile/mobile.scss | 1 + .../mobile/openid_connect_error_page.scss | 9 +++++++++ .../openid_connect/authorizations_controller.rb | 15 ++++++++++----- .../api/openid_connect/error/_error.html.haml | 6 +++++- .../api/openid_connect/error/error.mobile.haml | 7 +++++++ config/locales/diaspora/en.yml | 3 +++ 6 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 app/assets/stylesheets/mobile/openid_connect_error_page.scss diff --git a/app/assets/stylesheets/mobile/mobile.scss b/app/assets/stylesheets/mobile/mobile.scss index 4ad15e0e3..5b76b4acb 100644 --- a/app/assets/stylesheets/mobile/mobile.scss +++ b/app/assets/stylesheets/mobile/mobile.scss @@ -13,6 +13,7 @@ @import "mobile/settings"; @import "mobile/stream_element"; @import "mobile/comments"; +@import 'mobile/openid_connect_error_page'; @import 'typography'; diff --git a/app/assets/stylesheets/mobile/openid_connect_error_page.scss b/app/assets/stylesheets/mobile/openid_connect_error_page.scss new file mode 100644 index 000000000..c87efeb4e --- /dev/null +++ b/app/assets/stylesheets/mobile/openid_connect_error_page.scss @@ -0,0 +1,9 @@ +.landing { margin: -56px -20px 10px; } + +.api-error { + background-color: $light-grey; + box-shadow: $card-shadow; + margin-top: 20px; + + h4 { text-align: center; } +} diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 7acddcfaf..5dae076e5 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -177,7 +177,7 @@ module Api if params[:client_id] && params[:redirect_uri] handle_params_error_when_client_id_and_redirect_uri_exists(error, error_description) else - render_error error_description + render_error I18n.t("api.openid_connect.error_page.could_not_authorize"), error_description end end @@ -186,7 +186,8 @@ module Api if app && app.redirect_uris.include?(params[:redirect_uri]) redirect_prompt_error_display(error, error_description) else - render_error "Invalid client id or redirect uri" + render_error I18n.t("api.openid_connect.error_page.could_not_authorize"), + "Invalid client id or redirect uri" end end @@ -239,10 +240,14 @@ module Api redirect_to new_api_openid_connect_authorization_path(params) end - def render_error(error_description) + def render_error(error_description, detailed_error=nil) @error_description = error_description - render "api/openid_connect/error/error", - layout: request.format == :mobile ? "application" : "with_header_with_footer" + @detailed_error = detailed_error + if request.format == :mobile + render "api/openid_connect/error/error.mobile", layout: "application.mobile" + else + render "api/openid_connect/error/error", layout: "with_header_with_footer" + end end end end diff --git a/app/views/api/openid_connect/error/_error.html.haml b/app/views/api/openid_connect/error/_error.html.haml index 78ff3f9f4..de8d782b0 100644 --- a/app/views/api/openid_connect/error/_error.html.haml +++ b/app/views/api/openid_connect/error/_error.html.haml @@ -4,4 +4,8 @@ %h4 %b= t("api.openid_connect.error_page.title") %div{id: "openid_connect_error_description"} - = @error_description + %p= @error_description + - unless @detailed_error.nil? + %p= t("api.openid_connect.error_page.contact_developer") + %pre= @detailed_error + diff --git a/app/views/api/openid_connect/error/error.mobile.haml b/app/views/api/openid_connect/error/error.mobile.haml index a9b15dbd0..dcfd1f5f8 100644 --- a/app/views/api/openid_connect/error/error.mobile.haml +++ b/app/views/api/openid_connect/error/error.mobile.haml @@ -1 +1,8 @@ +.landing + %h1.session + = pod_name + = render partial: "api/openid_connect/error/error" + +%footer + = link_to t("layouts.application.toggle"), toggle_mobile_path diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 349c027df..09b20142c 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -919,6 +919,9 @@ en: description: "This allows the application to send new posts, write conversations, and send reactions" error_page: title: "Oh! Something went wrong :(" + contact_developer: "You should contact the developer of the application and include the following detailed message error:" + login_required: "You must first login before authorize this application" + could_not_authorize: "The application could not be authorized" people: zero: "No people" From 58aef5658bf13ec98308f0cff94fb177e1d4ee57 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Mon, 4 Jan 2016 16:26:00 +0900 Subject: [PATCH 103/105] Fix remaining remarks --- .../authorizations_controller.rb | 2 +- .../token_endpoint_controller.rb | 1 - .../api/openid_connect/o_auth_application.rb | 7 +- config/initializers/cors.rb | 8 +- config/locales/diaspora/en.yml | 4 +- .../step_definitions/oidc_common_steps.rb | 2 +- .../authorizations_controller_spec.rb | 2 +- .../openid_connect/clients_controller_spec.rb | 97 ++++++++++--------- 8 files changed, 65 insertions(+), 58 deletions(-) diff --git a/app/controllers/api/openid_connect/authorizations_controller.rb b/app/controllers/api/openid_connect/authorizations_controller.rb index 5dae076e5..36417e20b 100644 --- a/app/controllers/api/openid_connect/authorizations_controller.rb +++ b/app/controllers/api/openid_connect/authorizations_controller.rb @@ -97,7 +97,7 @@ module Api if seconds.nil? false else - (Time.zone.now.utc.to_i - current_user.current_sign_in_at.to_i) > seconds.to_i + (Time.now - current_user.current_sign_in_at) > seconds.to_i end end diff --git a/app/controllers/api/openid_connect/token_endpoint_controller.rb b/app/controllers/api/openid_connect/token_endpoint_controller.rb index 9f145058d..36b0ed31c 100644 --- a/app/controllers/api/openid_connect/token_endpoint_controller.rb +++ b/app/controllers/api/openid_connect/token_endpoint_controller.rb @@ -55,7 +55,6 @@ module Api logger.info e.backtrace[0, 10].join("\n") render json: {error: :invalid_grant, error_description: e.message, status: 400} end - end end end diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 57d5fe27f..6f5363669 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -76,13 +76,14 @@ module Api supported_metadata.each_with_object({}) do |key, attr| value = registrar.public_send(key) next unless value - if key == :subject_type + case key + when :subject_type attr[:ppid] = (value == "pairwise") - elsif key == :jwks_uri + when :jwks_uri response = Faraday.get(value) attr[:jwks] = response.body attr[:jwks_uri] = value - elsif key == :jwks + when :jwks attr[:jwks] = value.to_json else attr[key] = value diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 9ad8a18a5..a50aead3e 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -1,11 +1,11 @@ Rails.application.config.middleware.insert 0, Rack::Cors do allow do origins "*" - resource "/.well-known/host-meta" - resource "/webfinger" - resource "/.well-known/webfinger" - resource "/.well-known/openid-configuration" resource "/api/openid_connect/user_info", methods: %i(get post) resource "/api/v0/*", methods: %i(delete get post) + resource "/.well-known/host-meta" + resource "/.well-known/webfinger" + resource "/.well-known/openid-configuration" + resource "/webfinger" end end diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 09b20142c..78c63c74c 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -919,8 +919,8 @@ en: description: "This allows the application to send new posts, write conversations, and send reactions" error_page: title: "Oh! Something went wrong :(" - contact_developer: "You should contact the developer of the application and include the following detailed message error:" - login_required: "You must first login before authorize this application" + contact_developer: "You should contact the developer of the application and include the following detailed error message:" + login_required: "You must first login before you can authorize this application" could_not_authorize: "The application could not be authorized" people: diff --git a/features/step_definitions/oidc_common_steps.rb b/features/step_definitions/oidc_common_steps.rb index 743dfd8a7..4e28cd5f2 100644 --- a/features/step_definitions/oidc_common_steps.rb +++ b/features/step_definitions/oidc_common_steps.rb @@ -35,5 +35,5 @@ Then /^I should receive an "([^\"]*)" error$/ do |error_message| end Then(/^I should see a message containing "(.*?)"$/) do |message| - expect(find("#openid_connect_error_description").text).to eq(message) + expect(find("#openid_connect_error_description").text).to include(message) end diff --git a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb index 0c7e4ab0f..09b7bc626 100644 --- a/spec/controllers/api/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/api/openid_connect/authorizations_controller_spec.rb @@ -356,7 +356,7 @@ describe Api::OpenidConnect::AuthorizationsController, type: :controller do it "raises an error" do delete :destroy, id: 123_456_789 expect(response).to redirect_to(api_openid_connect_user_applications_url) - expect(flash[:error]).to eq("The attempt to revoke the authorization with ID 123456789 has failed") + expect(flash[:error]).to eq("The attempt to revoke the authorization with ID 123456789 failed") end end end diff --git a/spec/controllers/api/openid_connect/clients_controller_spec.rb b/spec/controllers/api/openid_connect/clients_controller_spec.rb index 19129bc49..164c82bce 100644 --- a/spec/controllers/api/openid_connect/clients_controller_spec.rb +++ b/spec/controllers/api/openid_connect/clients_controller_spec.rb @@ -5,8 +5,11 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters are passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "User-Agent" => "Faraday v0.9.2"}) + .with(headers: { + "Accept" => "*/*", + "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "User-Agent" => "Faraday v0.9.2" + }) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -22,8 +25,10 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters with jwks is passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "User-Agent" => "Faraday v0.9.2"}) + .with(headers: { + "Accept" => "*/*", + "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "User-Agent" => "Faraday v0.9.2"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", response_types: [], grant_types: [], application_type: "web", contacts: [], @@ -33,43 +38,43 @@ describe Api::OpenidConnect::ClientsController, type: :controller do token_endpoint_auth_method: "private_key_jwt", jwks: { keys: - [ - { - use: "enc", - e: "AQAB", - d: "-lTBWkI-----lvCO6tuiDsR4qgJnUwnndQFwEI_4mLmD3iNWXrc8N--5Cjq55eLtuJjtvuQ", - n: "--zYRQNDvIVsBDLQQIgrbctuGqj6lrXb31Jj3JIEYqH_4h5X9d0Q", - q: "1q-r----pFtyTz_JksYYaotc_Z3Zy-Szw6a39IDbuYGy1qL-15oQuc", - p: "-BfRjdgYouy4c6xAnGDgSMTip1YnPRyvbMaoYT9E_tEcBW5wOeoc", - kid: "a0", - kty: "RSA" - }, - { - use: "sig", - e: "AQAB", - d: "--x-gW---LRPowKrdvTuTo2p--HMI0pIEeFs7H_u5OW3jihjvoFClGPynHQhgWmQzlQRvWRXh6FhDVqFeGQ", - n: "---TyeadDqQPWgbqX69UzcGq5irhzN8cpZ_JaTk3Y_uV6owanTZLVvCgdjaAnMYeZhb0KFw", - q: "5E5XKK5njT--Hx3nF5sne5fleVfU-sZy6Za4B2U75PcE62oZgCPauOTAEm9Xuvrt5aMMovyzR8ecJZhm9bw7naU", - p: "-BUGA-", - kid: "a1", - kty: "RSA"}, - { - use: "sig", - crv: "P-256", - kty: "EC", - y: "Yg4IRzHBMIsuQK2Oz0Uukp1aNDnpdoyk6QBMtmfGHQQ", - x: "L0WUeVlc9r6YJd6ie9duvOU1RHwxSkJKA37IK9B4Bpc", - kid: "a2" - }, - { - use: "enc", - crv: "P-256", - kty: "EC", - y: "E6E6g5_ziIZvfdAoACctnwOhuQYMvQzA259aftPn59M", - x: "Yu8_BQE2L0f1MqnK0GumZOaj_77Tx70-LoudyRUnLM4", - kid: "a3" - } - ] + [ + { + use: "enc", + e: "AQAB", + d: "-lTBWkI-----lvCO6tuiDsR4qgJnUwnndQFwEI_4mLmD3iNWXrc8N--5Cjq55eLtuJjtvuQ", + n: "--zYRQNDvIVsBDLQQIgrbctuGqj6lrXb31Jj3JIEYqH_4h5X9d0Q", + q: "1q-r----pFtyTz_JksYYaotc_Z3Zy-Szw6a39IDbuYGy1qL-15oQuc", + p: "-BfRjdgYouy4c6xAnGDgSMTip1YnPRyvbMaoYT9E_tEcBW5wOeoc", + kid: "a0", + kty: "RSA" + }, + { + use: "sig", + e: "AQAB", + d: "--x-gW---LRPowKrdvTuTo2p--HMI0pIEeFs7H_u5OW3jihjvoFClGPynHQhgWmQzlQRvWRXh6FhDVqFeGQ", + n: "---TyeadDqQPWgbqX69UzcGq5irhzN8cpZ_JaTk3Y_uV6owanTZLVvCgdjaAnMYeZhb0KFw", + q: "5E5XKK5njT--Hx3nF5sne5fleVfU-sZy6Za4B2U75PcE62oZgCPauOTAEm9Xuvrt5aMMovyzR8ecJZhm9bw7naU", + p: "-BUGA-", + kid: "a1", + kty: "RSA"}, + { + use: "sig", + crv: "P-256", + kty: "EC", + y: "Yg4IRzHBMIsuQK2Oz0Uukp1aNDnpdoyk6QBMtmfGHQQ", + x: "L0WUeVlc9r6YJd6ie9duvOU1RHwxSkJKA37IK9B4Bpc", + kid: "a2" + }, + { + use: "enc", + crv: "P-256", + kty: "EC", + y: "E6E6g5_ziIZvfdAoACctnwOhuQYMvQzA259aftPn59M", + x: "Yu8_BQE2L0f1MqnK0GumZOaj_77Tx70-LoudyRUnLM4", + kid: "a3" + } + ] } client_json = JSON.parse(response.body) expect(client_json["client_id"].length).to eq(32) @@ -80,12 +85,14 @@ describe Api::OpenidConnect::ClientsController, type: :controller do context "when valid parameters with jwks_uri is passed" do it "should return a client id" do stub_request(:get, "http://example.com/uris") - .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "User-Agent" => "Faraday v0.9.2"}) + .with(headers: {"Accept" => "*/*", + "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "User-Agent" => "Faraday v0.9.2"}) .to_return(status: 200, body: "[\"http://localhost\"]", headers: {}) stub_request(:get, "https://kentshikama.com/api/openid_connect/jwks.json") - .with(headers: {"Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", - "User-Agent" => "Faraday v0.9.2"}) + .with(headers: {"Accept" => "*/*", + "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", + "User-Agent" => "Faraday v0.9.2"}) .to_return(status: 200, body: "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"n\":\"qpW\",\"use\":\"sig\"}]}", headers: {}) post :create, redirect_uris: ["http://localhost"], client_name: "diaspora client", From b09ee8791278518029e2b5780bb3f9e5b6a2b322 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Mon, 4 Jan 2016 16:59:06 +0900 Subject: [PATCH 104/105] Update json-jwt legacy methods --- .../controllers/api/openid_connect/id_tokens_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/api/openid_connect/id_tokens_controller_spec.rb b/spec/controllers/api/openid_connect/id_tokens_controller_spec.rb index 6827863d5..ec8ab8643 100644 --- a/spec/controllers/api/openid_connect/id_tokens_controller_spec.rb +++ b/spec/controllers/api/openid_connect/id_tokens_controller_spec.rb @@ -10,7 +10,7 @@ describe Api::OpenidConnect::IdTokensController, type: :controller do json = JSON.parse(response.body).with_indifferent_access jwks = JSON::JWK::Set.new json[:keys] public_keys = jwks.map do |jwk| - JSON::JWK.decode jwk + JSON::JWK.new(jwk).to_key end public_key = public_keys.first expect(Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY.to_s).to eq(public_key.to_s) From 38439277d6673cc379486f037f492a47c57a6e57 Mon Sep 17 00:00:00 2001 From: theworldbright Date: Mon, 4 Jan 2016 17:22:44 +0900 Subject: [PATCH 105/105] Add licenses where appropriate --- app/controllers/api/LICENSE | 19 --------------- .../api/openid_connect/clients_controller.rb | 1 + .../openid_connect/discovery_controller.rb | 23 +++++++++++++++++++ .../openid_connect/id_tokens_controller.rb | 21 +++++++++++++++++ .../api/openid_connect/authorization.rb | 3 +++ app/models/api/openid_connect/id_token.rb | 23 +++++++++++++++++++ .../api/openid_connect/o_auth_access_token.rb | 23 +++++++++++++++++++ .../api/openid_connect/o_auth_application.rb | 23 +++++++++++++++++++ .../pairwise_pseudonymous_identifier.rb | 23 +++++++++++++++++++ ...150613202109_create_o_auth_applications.rb | 2 ++ ...50708153928_create_o_auth_access_tokens.rb | 2 ++ db/migrate/20150714055110_create_id_tokens.rb | 2 ++ ...reate_pairwise_pseudonymous_identifiers.rb | 2 ++ .../protected_resource_endpoint.rb | 23 +++++++++++++++++++ lib/api/openid_connect/token_endpoint.rb | 2 ++ 15 files changed, 173 insertions(+), 19 deletions(-) delete mode 100644 app/controllers/api/LICENSE diff --git a/app/controllers/api/LICENSE b/app/controllers/api/LICENSE deleted file mode 100644 index de7a5345d..000000000 --- a/app/controllers/api/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2012 Brian Ploetz (bploetz@gmail.com) - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/app/controllers/api/openid_connect/clients_controller.rb b/app/controllers/api/openid_connect/clients_controller.rb index 66ef31cc6..0a6f7ba94 100644 --- a/app/controllers/api/openid_connect/clients_controller.rb +++ b/app/controllers/api/openid_connect/clients_controller.rb @@ -18,6 +18,7 @@ module Api validation_fail_as_json(e) end + # Inspired by https://github.com/nov/openid_connect_sample/blob/master/app/controllers/connect/clients_controller.rb#L24 def create registrar = OpenIDConnect::Client::Registrar.new(request.url, params) client = Api::OpenidConnect::OAuthApplication.register! registrar diff --git a/app/controllers/api/openid_connect/discovery_controller.rb b/app/controllers/api/openid_connect/discovery_controller.rb index a2fe1b766..19c9001b4 100644 --- a/app/controllers/api/openid_connect/discovery_controller.rb +++ b/app/controllers/api/openid_connect/discovery_controller.rb @@ -1,3 +1,26 @@ +# Copyright (c) 2011 nov matake +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# See https://github.com/nov/openid_connect_sample/blob/master/app/controllers/discovery_controller.rb + module Api module OpenidConnect class DiscoveryController < ApplicationController diff --git a/app/controllers/api/openid_connect/id_tokens_controller.rb b/app/controllers/api/openid_connect/id_tokens_controller.rb index 1528b2733..26eb17bde 100644 --- a/app/controllers/api/openid_connect/id_tokens_controller.rb +++ b/app/controllers/api/openid_connect/id_tokens_controller.rb @@ -1,3 +1,24 @@ +# Copyright (c) 2011 nov matake +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + module Api module OpenidConnect class IdTokensController < ApplicationController diff --git a/app/models/api/openid_connect/authorization.rb b/app/models/api/openid_connect/authorization.rb index fb8c006cc..f0ecef411 100644 --- a/app/models/api/openid_connect/authorization.rb +++ b/app/models/api/openid_connect/authorization.rb @@ -1,3 +1,5 @@ +# Inspired by https://github.com/nov/openid_connect_sample/blob/master/app/models/authorization.rb + module Api module OpenidConnect class Authorization < ActiveRecord::Base @@ -29,6 +31,7 @@ module Api end end + # Inspired by https://github.com/nov/openid_connect_sample/blob/master/app/models/access_token.rb#L26 def accessible?(required_scopes=nil) Array(required_scopes).all? { |required_scope| scopes.include? required_scope diff --git a/app/models/api/openid_connect/id_token.rb b/app/models/api/openid_connect/id_token.rb index 4b8e83137..7fdcd7af0 100644 --- a/app/models/api/openid_connect/id_token.rb +++ b/app/models/api/openid_connect/id_token.rb @@ -1,3 +1,26 @@ +# Copyright (c) 2011 nov matake +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# See https://github.com/nov/openid_connect_sample/blob/master/app/models/id_token.rb + require "uri" module Api diff --git a/app/models/api/openid_connect/o_auth_access_token.rb b/app/models/api/openid_connect/o_auth_access_token.rb index a62b72ad9..053bc86df 100644 --- a/app/models/api/openid_connect/o_auth_access_token.rb +++ b/app/models/api/openid_connect/o_auth_access_token.rb @@ -1,3 +1,26 @@ +# Copyright (c) 2011 nov matake +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# See https://github.com/nov/openid_connect_sample/blob/master/app/models/access_token.rb + module Api module OpenidConnect class OAuthAccessToken < ActiveRecord::Base diff --git a/app/models/api/openid_connect/o_auth_application.rb b/app/models/api/openid_connect/o_auth_application.rb index 6f5363669..ccceadfea 100644 --- a/app/models/api/openid_connect/o_auth_application.rb +++ b/app/models/api/openid_connect/o_auth_application.rb @@ -1,3 +1,26 @@ +# Copyright (c) 2011 nov matake +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# See https://github.com/nov/openid_connect_sample/blob/master/app/models/client.rb + require "digest" module Api diff --git a/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb b/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb index 4821ea1a2..7aeccc9fa 100644 --- a/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb +++ b/app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb @@ -1,3 +1,26 @@ +# Copyright (c) 2011 nov matake +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# See https://github.com/nov/openid_connect_sample/blob/master/app/models/pairwise_pseudonymous_identifier.rb + module Api module OpenidConnect class PairwisePseudonymousIdentifier < ActiveRecord::Base diff --git a/db/migrate/20150613202109_create_o_auth_applications.rb b/db/migrate/20150613202109_create_o_auth_applications.rb index c40368622..1170b5c9e 100644 --- a/db/migrate/20150613202109_create_o_auth_applications.rb +++ b/db/migrate/20150613202109_create_o_auth_applications.rb @@ -1,3 +1,5 @@ +# Inspired by https://github.com/nov/openid_connect_sample/blob/master/db/migrate/20110829023826_create_clients.rb + class CreateOAuthApplications < ActiveRecord::Migration def change create_table :o_auth_applications do |t| diff --git a/db/migrate/20150708153928_create_o_auth_access_tokens.rb b/db/migrate/20150708153928_create_o_auth_access_tokens.rb index 5172ff7c6..d833011c5 100644 --- a/db/migrate/20150708153928_create_o_auth_access_tokens.rb +++ b/db/migrate/20150708153928_create_o_auth_access_tokens.rb @@ -1,3 +1,5 @@ +# Inspired by https://github.com/nov/openid_connect_sample/blob/master/db/migrate/20110829023837_create_access_tokens.rb + class CreateOAuthAccessTokens < ActiveRecord::Migration def change create_table :o_auth_access_tokens do |t| diff --git a/db/migrate/20150714055110_create_id_tokens.rb b/db/migrate/20150714055110_create_id_tokens.rb index 4a5af5985..b1e3abdfa 100644 --- a/db/migrate/20150714055110_create_id_tokens.rb +++ b/db/migrate/20150714055110_create_id_tokens.rb @@ -1,3 +1,5 @@ +# Inspired by https://github.com/nov/openid_connect_sample/blob/master/db/migrate/20110829024010_create_id_tokens.rb + class CreateIdTokens < ActiveRecord::Migration def change create_table :id_tokens do |t| diff --git a/db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb b/db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb index 40fc03d09..0c8690848 100644 --- a/db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb +++ b/db/migrate/20150731123113_create_pairwise_pseudonymous_identifiers.rb @@ -1,3 +1,5 @@ +# Inspired by https://github.com/nov/openid_connect_sample/blob/master/db/migrate/20110829024140_create_pairwise_pseudonymous_identifiers.rb + class CreatePairwisePseudonymousIdentifiers < ActiveRecord::Migration def change create_table :ppid do |t| diff --git a/lib/api/openid_connect/protected_resource_endpoint.rb b/lib/api/openid_connect/protected_resource_endpoint.rb index a64c5db45..540b69d1d 100644 --- a/lib/api/openid_connect/protected_resource_endpoint.rb +++ b/lib/api/openid_connect/protected_resource_endpoint.rb @@ -1,3 +1,26 @@ +# Copyright (c) 2011 nov matake +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# See https://github.com/nov/openid_connect_sample/blob/master/lib/authentication.rb#L56 + module Api module OpenidConnect module ProtectedResourceEndpoint diff --git a/lib/api/openid_connect/token_endpoint.rb b/lib/api/openid_connect/token_endpoint.rb index 627dc6737..a2e8c8ac2 100644 --- a/lib/api/openid_connect/token_endpoint.rb +++ b/lib/api/openid_connect/token_endpoint.rb @@ -1,3 +1,5 @@ +# Inspired by https://github.com/nov/openid_connect_sample/blob/master/lib/token_endpoint.rb + module Api module OpenidConnect class TokenEndpoint