Commit ce367f7

mokha <mokha@cisco.com>
2018-06-11 18:41:05
add authentication to scim API.
1 parent 92514e1
app/controllers/scim/v2/groups_controller.rb
@@ -7,7 +7,7 @@ module Scim
       def index
         render json: {
           schemas: [Scim::Shady::Messages::LIST_RESPONSE],
-          totalResults: 0,
+          totalResults: User.count,
           Resources: resources,
         }.to_json, status: :ok
       end
app/controllers/scim/controller.rb
@@ -8,7 +8,15 @@ module Scim
 
     private
 
-    def authenticate!; end
+    def current_user
+      @current_user ||= authenticate_with_http_token do |token|
+        User.authenticate_token(token)
+      end
+    end
+
+    def authenticate!
+      render plain: "Unauthorized", status: :unauthorized unless current_user?
+    end
 
     def not_found
       render json: {
app/models/bearer_token.rb
@@ -13,7 +13,8 @@ class BearerToken
   def decode(token)
     decoded = JWT.decode(token, public_key, true, algorithm: 'RS256')[0]
     decoded.with_indifferent_access
-  rescue StandardError
+  rescue StandardError => error
+    Rails.logger.error(error)
     {}
   end
 
@@ -24,7 +25,7 @@ class BearerToken
   def defaults
     issued_at = Time.current.to_i
     {
-      exp: 1.hour.from_now,
+      exp: 1.hour.from_now.to_i,
       iat: issued_at,
       iss: Saml::Kit.configuration.entity_id,
       nbf: issued_at,
app/models/user.rb
@@ -29,12 +29,18 @@ class User < ApplicationRecord
     nil
   end
 
-  private
+  def self.authenticate_token(token)
+    token = BearerToken.new.decode(token)
+    return if token.empty?
+    User.find_by(uuid: token[:sub])
+  end
 
   def access_token(audience)
     BearerToken.new.encode(sub: uuid, aud: audience)
   end
 
+  private
+
   def trusted_attributes_for(request)
     {
       id: uuid,
spec/requests/scim/v2/groups_spec.rb
@@ -1,18 +1,18 @@
 require 'rails_helper'
 
 describe "/scim/v2/groups" do
-  let(:token) { SecureRandom.uuid }
-  let(:headers) do
-    {
-      'Authorization' => "Bearer #{token}",
-      'Accept' => 'application/scim+json',
-      'Content-Type' => 'application/scim+json',
-    }
-  end
+  context "when authenticated" do
+    let(:user) { create(:user) }
+    let(:token) { user.access_token('unknown') }
+    let(:headers) do
+      {
+        'Authorization' => "Bearer #{token}",
+        'Accept' => 'application/scim+json',
+        'Content-Type' => 'application/scim+json',
+      }
+    end
 
-  describe "GET /scim/v2/groups" do
-    context "when retrieving all groups" do
-      let!(:user) { create(:user) }
+    describe "GET /scim/v2/groups" do
       before { get '/scim/v2/groups', headers: headers }
 
       specify { expect(response).to have_http_status(:ok) }
@@ -24,4 +24,17 @@ describe "/scim/v2/groups" do
       specify { expect(json[:Resources]).to match_array([id: user.uuid, userName: user.email]) }
     end
   end
+
+  context "when the authentication token is invalid" do
+    let(:bad_headers) do
+      {
+        'Authorization' => "Bearer #{SecureRandom.uuid}",
+        'Accept' => 'application/scim+json',
+        'Content-Type' => 'application/scim+json',
+      }
+    end
+    before { get '/scim/v2/groups', headers: bad_headers }
+
+    specify { expect(response).to have_http_status(:unauthorized) }
+  end
 end
spec/requests/scim/v2/search_spec.rb
@@ -1,7 +1,8 @@
 require 'rails_helper'
 
 describe '/scim/v1/.search' do
-  let(:token) { SecureRandom.uuid }
+  let(:user) { create(:user) }
+  let(:token) { user.access_token('rspec') }
   let(:headers) do
     {
       'Authorization' => "Bearer #{token}",
spec/requests/scim/v2/service_provider_config_spec.rb
@@ -1,8 +1,18 @@
 require 'rails_helper'
 
 describe "/ServiceProviderConfig" do
+  let(:user) { create(:user) }
+  let(:token) { user.access_token('rspec') }
+  let(:headers) do
+    {
+      'Authorization' => "Bearer #{token}",
+      'Accept' => 'application/scim+json',
+      'Content-Type' => 'application/scim+json',
+    }
+  end
+
   it 'returns a 200' do
-    get '/scim/v2/ServiceProviderConfig'
+    get '/scim/v2/ServiceProviderConfig', headers: headers
 
     expect(response).to have_http_status(:ok)
     expect(response.body).to be_present
spec/requests/scim/v2/users_spec.rb
@@ -1,7 +1,8 @@
 require 'rails_helper'
 
 describe '/scim/v2/users' do
-  let(:token) { SecureRandom.uuid }
+  let(:user) { create(:user) }
+  let(:token) { user.access_token("rspec") }
   let(:headers) do
     {
       'Authorization' => "Bearer #{token}",
@@ -100,18 +101,18 @@ describe '/scim/v2/users' do
   end
 
   describe "DELETE /scim/v2/users/:id" do
-    let(:user) { create(:user) }
+    let(:other_user) { create(:user) }
 
     it 'deletes the user' do
-      delete "/scim/v2/users/#{user.uuid}", headers: headers
+      delete "/scim/v2/users/#{other_user.uuid}", headers: headers
       expect(response).to have_http_status(:no_content)
 
-      get "/scim/v2/users/#{user.uuid}", headers: headers
+      get "/scim/v2/users/#{other_user.uuid}", headers: headers
       expect(response).to have_http_status(:not_found)
       expect(response.body).to be_present
       json = JSON.parse(response.body, symbolize_names: true)
       expect(json[:schemas]).to match_array([Scim::Shady::Messages::ERROR])
-      expect(json[:detail]).to eql("Resource #{user.uuid} not found")
+      expect(json[:detail]).to eql("Resource #{other_user.uuid} not found")
       expect(json[:status]).to eql("404")
     end
   end
spec/factories.rb
@@ -1,7 +1,7 @@
 FactoryBot.define do
   factory :user do
-    email FFaker::Internet.email
-    uuid SecureRandom.uuid
-    password FFaker::Internet.password
+    email { FFaker::Internet.email }
+    uuid { SecureRandom.uuid }
+    password { FFaker::Internet.password }
   end
 end