theworldbright 2015-10-03 17:47:23 -07:00
parent 1dcefdb998
commit fd467cd42b
13 changed files with 307 additions and 21 deletions

1
.gitignore vendored
View file

@ -21,6 +21,7 @@ config/database.yml
.rvmrc_custom .rvmrc_custom
.rvmrc.local .rvmrc.local
config/oidc_key.pem config/oidc_key.pem
config/jwks/
# Mailing list stuff # Mailing list stuff
config/email_offset config/email_offset

View file

@ -32,7 +32,7 @@ module Api
private private
def http_error_page_as_json(e) 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 end
def validation_fail_as_json(e) def validation_fail_as_json(e)

View file

@ -25,7 +25,7 @@ module Api
request_object_signing_alg_values_supported: %i(HS256 HS384 HS512), request_object_signing_alg_values_supported: %i(HS256 HS384 HS512),
subject_types_supported: %w(public pairwise), subject_types_supported: %w(public pairwise),
id_token_signing_alg_values_supported: %i(RS256), 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) claims_supported: %w(sub nickname profile picture)
) )
end end

View file

@ -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

View file

@ -1,3 +1,5 @@
require "digest"
module Api module Api
module OpenidConnect module OpenidConnect
class OAuthApplication < ActiveRecord::Base class OAuthApplication < ActiveRecord::Base
@ -68,7 +70,7 @@ module Api
def supported_metadata def supported_metadata
%i(client_name response_types grant_types application_type %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) sector_identifier_uri subject_type token_endpoint_auth_method jwks jwks_uri)
end end
def registrar_attributes(registrar) def registrar_attributes(registrar)
@ -77,11 +79,30 @@ module Api
next unless value next unless value
if key == :subject_type if key == :subject_type
attr[:ppid] = (value == "pairwise") 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 else
attr[key] = value attr[key] = value
end end
end 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 end
end end

View file

@ -242,7 +242,7 @@ Diaspora::Application.routes.draw do
resources :clients, only: :create resources :clients, only: :create
get "clients/find", to: "clients#find" 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 # 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 # See http://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation

View file

@ -16,6 +16,10 @@ class CreateOAuthApplications < ActiveRecord::Migration
t.string :policy_uri t.string :policy_uri
t.string :tos_uri t.string :tos_uri
t.string :sector_identifier_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.boolean :ppid, default: false
t.timestamps null: false t.timestamps null: false

View file

@ -287,6 +287,9 @@ ActiveRecord::Schema.define(version: 20150828132451) do
t.string "policy_uri", limit: 255 t.string "policy_uri", limit: 255
t.string "tos_uri", limit: 255 t.string "tos_uri", limit: 255
t.string "sector_identifier_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.boolean "ppid", default: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false

View file

@ -19,6 +19,87 @@ describe Api::OpenidConnect::ClientsController, type: :controller do
end end
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 context "when redirect uri is missing" do
it "should return a invalid_client_metadata error" do it "should return a invalid_client_metadata error" do
post :create, response_types: [], grant_types: [], application_type: "web", contacts: [], post :create, response_types: [], grant_types: [], application_type: "web", contacts: [],

View file

@ -328,6 +328,13 @@ FactoryGirl.define do
sector_identifier_uri "https://example.com/uri" sector_identifier_uri "https://example.com/uri"
end 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 factory :o_auth_application_with_multiple_redirects, class: Api::OpenidConnect::OAuthApplication do
client_name "Diaspora Test Client" client_name "Diaspora Test Client"
redirect_uris %w(http://localhost:3000/ http://localhost/) redirect_uris %w(http://localhost:3000/ http://localhost/)

1
spec/fixtures/jwks.json vendored Normal file
View file

@ -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"}]}

View file

@ -7,6 +7,16 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do
o_auth_application: client, user: bob, redirect_uri: "http://localhost:3000/", scopes: ["openid"]) o_auth_application: client, user: bob, redirect_uri: "http://localhost:3000/", scopes: ["openid"])
} }
let!(:code) { auth.create_code } 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 describe "the authorization code grant type" do
context "when the authorization code is valid" do context "when the authorization code is valid" do
@ -53,6 +63,44 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do
end end
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 context "when the authorization code is not valid" do
it "should return an invalid grant error" do it "should return an invalid grant error" do
post api_openid_connect_access_tokens_path, grant_type: "authorization_code", post api_openid_connect_access_tokens_path, grant_type: "authorization_code",
@ -61,6 +109,45 @@ describe Api::OpenidConnect::TokenEndpoint, type: :request do
end end
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 context "when the client is unregistered" do
it "should return an error" do it "should return an error" do
post api_openid_connect_access_tokens_path, grant_type: "authorization_code", code: auth.refresh_token, 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
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 context "when the code field is missing" do
it "should return an invalid request error" do it "should return an invalid request error" do
post api_openid_connect_access_tokens_path, grant_type: "authorization_code", post api_openid_connect_access_tokens_path, grant_type: "authorization_code",

View file

@ -59,6 +59,10 @@ def photo_fixture_name
@photo_fixture_name = File.join(File.dirname(__FILE__), "fixtures", "button.png") @photo_fixture_name = File.join(File.dirname(__FILE__), "fixtures", "button.png")
end end
def jwks_file_path
@jwks_file = "../../spec/fixtures/jwks.json"
end
# Force fixture rebuild # Force fixture rebuild
FileUtils.rm_f(Rails.root.join("tmp", "fixture_builder.yml")) FileUtils.rm_f(Rails.root.join("tmp", "fixture_builder.yml"))