Commit cb239d2

mo <mo@mokhan.ca>
2018-10-20 16:13:33
start to implement RFC7591 dynamic client registration
1 parent 957d320
Changed files (6)
app/controllers/clients_controller.rb
@@ -0,0 +1,27 @@
+class ClientsController < ApplicationController
+  skip_before_action :authenticate!
+  before_action :apply_cache_headers
+
+  def create
+    @client = Client.create!(transform(secure_params))
+    render status: :created, formats: :json
+  end
+
+  private
+
+  def secure_params
+    params.permit(:client_name, :token_endpoint_auth_method, :logo_uri, :jwks_uri, redirect_uris: [])
+  end
+
+  def transform(params)
+    {
+      name: params[:client_name],
+      redirect_uri: params[:redirect_uris][0],
+    }
+  end
+
+  def apply_cache_headers
+    response.headers["Cache-Control"] = "no-cache, no-store"
+    response.headers["Pragma"] = "no-cache"
+  end
+end
app/models/client.rb
@@ -15,6 +15,18 @@ class Client < ApplicationRecord
     self.password = SecureRandom.base58(24) unless password_digest
   end
 
+  def redirect_uris
+    [redirect_uri]
+  end
+
+  def grant_types
+    [:authorization_code, :refresh_token, :client_credentials, :password, 'urn:ietf:params:oauth:grant-type:saml2-bearer']
+  end
+
+  def token_endpoint_auth_method
+    :client_secret_basic
+  end
+
   def access_token
     transaction do
       Token
app/views/clients/create.json.jbuilder
@@ -0,0 +1,10 @@
+json.client_id @client.to_param
+json.client_secret @client.password
+json.client_id_issued_at @client.created_at.to_i
+json.client_secret_expires_at 0
+json.redirect_uris @client.redirect_uris
+json.grant_types @client.grant_types
+json.client_name @client.name
+json.token_endpoint_auth_method @client.token_endpoint_auth_method
+json.logo_uri @client.logo_uri
+json.jwks_uri @client.jwks_uri
config/routes.rb
@@ -9,6 +9,7 @@ Rails.application.routes.draw do
     get :authorize, to: "oauths#show"
   end
   resource :session, only: [:new, :create, :destroy]
+  resources :clients, only: [:create]
   resources :registrations, only: [:new, :create]
   resource :response, only: [:show]
   resource :tokens, only: [:create] do
spec/requests/clients_spec.rb
@@ -0,0 +1,71 @@
+require 'rails_helper'
+
+RSpec.describe "/clients" do
+  describe "POST /clients" do
+    let(:redirect_uris) { [generate(:uri), generate(:uri)] }
+    let(:client_name) { FFaker::Name.name }
+    let(:logo_uri) { generate(:uri) }
+    let(:jwks_uri) { generate(:uri) }
+    let(:json) { JSON.parse(response.body, symbolize_names: true) }
+    let(:last_client) { Client.order(created_at: :asc).last }
+
+    context "when the registration request is valid" do
+      before do
+        post "/clients", params: {
+          redirect_uris: redirect_uris,
+          client_name: client_name,
+          token_endpoint_auth_method: :client_secret_basic,
+          logo_uri: logo_uri,
+          jwks_uri: jwks_uri,
+        }
+      end
+
+      specify { expect(response).to have_http_status(:created) }
+      specify { expect(response.headers['Content-Type']).to include("application/json") }
+      specify { expect(response.headers['Cache-Control']).to include("no-store") }
+      specify { expect(response.headers['Pragma']).to eql("no-cache") }
+      specify { expect(json[:client_id]).to eql(last_client.uuid) }
+      specify { expect(json[:client_secret]).to be_present }
+      specify { expect(json[:client_id_issued_at]).to eql(last_client.created_at.to_i) }
+      specify { expect(json[:client_secret_expires_at]).to be_zero }
+      specify { expect(json[:redirect_uris]).to match_array(redirect_uris) }
+      specify { expect(json[:grant_types]).to match_array(last_client.grant_types.map(&:to_s)) }
+      specify { expect(json[:client_name]).to eql(client_name) }
+      specify { expect(json[:token_endpoint_auth_method]).to eql('client_secret_basic') }
+      specify { expect(json[:logo_uri]).to eql(logo_uri) }
+      specify { expect(json[:jwks_uri]).to eql(jwks_uri) }
+    end
+
+    context "when the registrations is missing valid redirect_uris" do
+      before do
+        post "/clients", params: {
+          redirect_uris: [],
+          client_name: client_name,
+          token_endpoint_auth_method: :client_secret_basic,
+          logo_uri: logo_uri,
+          jwks_uri: jwks_uri,
+        }
+      end
+
+      specify { expect(response).to have_http_status(:bad_request) }
+      specify { expect(json[:error]).to eql("invalid_redirect_uri") }
+      specify { expect(json[:error_description]).to be_present }
+    end
+
+    context "when the registration request is missing a client name" do
+      before do
+        post "/clients", params: {
+          redirect_uris: redirect_uris,
+          client_name: "",
+          token_endpoint_auth_method: :client_secret_basic,
+          logo_uri: logo_uri,
+          jwks_uri: jwks_uri,
+        }
+      end
+
+      specify { expect(response).to have_http_status(:bad_request) }
+      specify { expect(json[:error]).to eql("invalid_client_metadata") }
+      specify { expect(json[:error_description]).to be_present }
+    end
+  end
+end
spec/factories.rb
@@ -3,4 +3,5 @@
 FactoryBot.define do
   sequence(:email) { |_n| FFaker::Internet.email }
   sequence(:password) { |_n| FFaker::Internet.password }
+  sequence(:uri) { |_n| FFaker::Internet.uri('https') }
 end