Commit fdd4ff60 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'dblessing_okta_scim' into 'master'

Add support for Okta as SCIM provider

See merge request gitlab-org/gitlab!25649
parents da53916e 1ea5d5ca
---
title: Add support for Okta as a SCIM provider
merge_request: 25649
author:
type: added
...@@ -49,6 +49,9 @@ class ScimFinder ...@@ -49,6 +49,9 @@ class ScimFinder
if eq_filter_on_extern_uid?(parser) if eq_filter_on_extern_uid?(parser)
by_extern_uid(parser.filter_params[:extern_uid]) by_extern_uid(parser.filter_params[:extern_uid])
elsif eq_filter_on_username?(parser) elsif eq_filter_on_username?(parser)
identity = by_extern_uid(parser.filter_params[:username])
return identity if identity.present?
by_username(parser.filter_params[:username]) by_username(parser.filter_params[:username])
else else
raise UnsupportedFilter raise UnsupportedFilter
......
...@@ -10,9 +10,10 @@ module EE ...@@ -10,9 +10,10 @@ module EE
expose :active expose :active
expose :email_user, as: :emails, using: ::EE::API::Entities::Scim::Emails expose :email_user, as: :emails, using: ::EE::API::Entities::Scim::Emails
expose 'name.formatted' do |identity, _options| expose :name, using: ::EE::API::Entities::Scim::UserName do |identity, _options|
identity.user.name identity.user
end end
expose :meta do expose :meta do
expose :resource_type, as: :resourceType expose :resource_type, as: :resourceType
end end
......
# frozen_string_literal: true
module EE
module API
module Entities
module Scim
class UserName < Grape::Entity
expose :name, as: :formatted
expose :first_name, as: :givenName
expose :last_name, as: :familyName
end
end
end
end
end
...@@ -4,7 +4,7 @@ module EE ...@@ -4,7 +4,7 @@ module EE
module Gitlab module Gitlab
module Scim module Scim
class ParamsParser class ParamsParser
OPERATIONS_OPERATORS = %w[Replace Add].freeze OPERATIONS_OPERATORS = %w[replace add].freeze
def initialize(params) def initialize(params)
@params = params.with_indifferent_access @params = params.with_indifferent_access
...@@ -43,9 +43,9 @@ module EE ...@@ -43,9 +43,9 @@ module EE
def process_operations def process_operations
@params[:Operations].each_with_object({}) do |operation, hash| @params[:Operations].each_with_object({}) do |operation, hash|
next unless OPERATIONS_OPERATORS.include?(operation[:op]) next unless OPERATIONS_OPERATORS.include?(operation[:op].downcase)
hash.merge!(AttributeTransform.new(operation[:path]).map_to(operation[:value])) hash.merge!(transformed_operation(operation))
end end
end end
...@@ -76,6 +76,19 @@ module EE ...@@ -76,6 +76,19 @@ module EE
formatted_name ||= [name[:givenName], name[:familyName]].compact.join(' ') formatted_name ||= [name[:givenName], name[:familyName]].compact.join(' ')
@hash[:name] = formatted_name @hash[:name] = formatted_name
end end
# The `path` key is optional per the SCIM spec.
# Ex. Azure uses `path` while Okta does not.
def transformed_operation(operation)
key, value =
if operation[:path]
operation.values_at(:path, :value)
else
[operation[:value].each_key.first, operation[:value].each_value.first]
end
AttributeTransform.new(key).map_to(value)
end
end end
end end
end end
......
...@@ -56,9 +56,19 @@ describe ScimFinder do ...@@ -56,9 +56,19 @@ describe ScimFinder do
expect(finder.search(filter: "userName eq \"#{id.user.username}\"").first).to eq id expect(finder.search(filter: "userName eq \"#{id.user.username}\"").first).to eq id
end end
it 'allows lookup by userName when userName is an email address' do context 'allows lookup by userName' do
it 'finds user when userName is an email address' do
expect(finder.search(filter: "userName eq #{id.user.email}").first).to eq id expect(finder.search(filter: "userName eq #{id.user.email}").first).to eq id
end end
it 'finds user by username' do
expect(finder.search(filter: "userName eq \"#{id.user.username}\"").first).to eq id
end
it 'finds user by extern_uid' do
expect(finder.search(filter: "userName eq \"#{id.extern_uid}\"").first).to eq id
end
end
end end
context 'when scim_identities is disabled' do context 'when scim_identities is disabled' do
......
# frozen_string_literal: true
require 'spec_helper'
describe ::EE::API::Entities::Scim::UserName do
let(:user) { build(:user) }
subject { described_class.new(user).as_json }
it 'contains the name' do
expect(subject[:formatted]).to eq(user.name)
end
it 'contains the first name' do
expect(subject[:givenName]).to eq(user.first_name)
end
it 'contains the last name' do
expect(subject[:familyName]).to eq(user.last_name)
end
end
...@@ -25,7 +25,15 @@ describe ::EE::API::Entities::Scim::User do ...@@ -25,7 +25,15 @@ describe ::EE::API::Entities::Scim::User do
end end
it 'contains the name' do it 'contains the name' do
expect(subject[:'name.formatted']).to eq(user.name) expect(subject[:name][:formatted]).to eq(user.name)
end
it 'contains the first name' do
expect(subject[:name][:givenName]).to eq(user.first_name)
end
it 'contains the last name' do
expect(subject[:name][:familyName]).to eq(user.last_name)
end end
it 'contains the email' do it 'contains the email' do
......
...@@ -32,25 +32,59 @@ describe EE::Gitlab::Scim::ParamsParser do ...@@ -32,25 +32,59 @@ describe EE::Gitlab::Scim::ParamsParser do
end end
describe '#update_params' do describe '#update_params' do
shared_examples 'scim operation active false' do
it 'returns the correct operation attributes' do it 'returns the correct operation attributes' do
operations = [{ 'op': 'Replace', 'path': 'active', 'value': 'False' }]
expect(described_class.new(Operations: operations).update_params).to eq(active: false) expect(described_class.new(Operations: operations).update_params).to eq(active: false)
end end
end
shared_examples 'scim operation empty' do
it 'returns an empty hash for the wrong operations' do it 'returns an empty hash for the wrong operations' do
operations = [{ 'op': 'Replace', 'path': 'test', 'value': 'False' }]
expect(described_class.new(Operations: operations).update_params).to eq({}) expect(described_class.new(Operations: operations).update_params).to eq({})
end end
end
shared_examples 'scim operation update name' do
it 'can update name from displayName' do it 'can update name from displayName' do
operations = [{ 'op': 'Replace', 'path': 'displayName', 'value': 'My Name Is' }]
expect(described_class.new(Operations: operations).update_params).to include(name: 'My Name Is') expect(described_class.new(Operations: operations).update_params).to include(name: 'My Name Is')
end end
end end
context 'when path key is present' do
it_behaves_like 'scim operation active false' do
let(:operations) { [{ 'op': 'replace', 'path': 'active', 'value': 'False' }] }
end
it_behaves_like 'scim operation empty' do
let(:operations) { [{ 'op': 'replace', 'path': 'test', 'value': 'False' }] }
end
it_behaves_like 'scim operation update name' do
let(:operations) { [{ 'op': 'replace', 'path': 'displayName', 'value': 'My Name Is' }] }
end
end
context 'when path key is not present' do
it_behaves_like 'scim operation active false' do
let(:operations) { [{ 'op': 'replace', 'value': { 'active': false } }] }
end
it_behaves_like 'scim operation empty' do
let(:operations) { [{ 'op': 'replace', 'value': { 'test': false } }] }
end
it_behaves_like 'scim operation update name' do
let(:operations) { [{ 'op': 'replace', 'value': { 'displayName': 'My Name Is' } }] }
end
end
context 'with capitalized op values for Azure' do
it_behaves_like 'scim operation active false' do
let(:operations) { [{ 'op': 'Replace', 'path': 'active', 'value': 'False' }] }
end
end
end
describe '#post_params' do describe '#post_params' do
it 'returns a parsed hash for POST params' do it 'returns a parsed hash for POST params' do
params = { params = {
...@@ -87,27 +121,33 @@ describe EE::Gitlab::Scim::ParamsParser do ...@@ -87,27 +121,33 @@ describe EE::Gitlab::Scim::ParamsParser do
describe '#deprovision_user?' do describe '#deprovision_user?' do
it 'returns true when deprovisioning' do it 'returns true when deprovisioning' do
operations = [{ 'op': 'Replace', 'path': 'active', 'value': 'False' }] operations = [{ 'op': 'replace', 'path': 'active', 'value': 'False' }]
expect(described_class.new(Operations: operations).deprovision_user?).to be true expect(described_class.new(Operations: operations).deprovision_user?).to be true
end end
it 'returns false when not deprovisioning' do it 'returns false when not deprovisioning' do
operations = [{ 'op': 'Replace', 'path': 'active', 'value': 'True' }] operations = [{ 'op': 'replace', 'path': 'active', 'value': 'True' }]
expect(described_class.new(Operations: operations).deprovision_user?).to be false expect(described_class.new(Operations: operations).deprovision_user?).to be false
end end
it 'returns true when deprovisioning without a path key' do
operations = [{ 'op': 'replace', 'value': { 'active': false } }]
expect(described_class.new(Operations: operations).deprovision_user?).to be true
end
end end
describe '#reprovision_user?' do describe '#reprovision_user?' do
it 'returns true when reprovisioning' do it 'returns true when reprovisioning' do
operations = [{ 'op': 'Replace', 'path': 'active', 'value': 'True' }] operations = [{ 'op': 'replace', 'path': 'active', 'value': 'True' }]
expect(described_class.new(Operations: operations).reprovision_user?).to be true expect(described_class.new(Operations: operations).reprovision_user?).to be true
end end
it 'returns false when not reprovisioning' do it 'returns false when not reprovisioning' do
operations = [{ 'op': 'Replace', 'path': 'active', 'value': 'False' }] operations = [{ 'op': 'replace', 'path': 'active', 'value': 'False' }]
expect(described_class.new(Operations: operations).reprovision_user?).to be false expect(described_class.new(Operations: operations).reprovision_user?).to be false
end end
......
...@@ -277,18 +277,6 @@ describe API::Scim do ...@@ -277,18 +277,6 @@ describe API::Scim do
end end
end end
end end
context 'Remove user' do
before do
params = { Operations: [{ 'op': 'Replace', 'path': 'active', 'value': 'False' }] }.to_query
patch scim_api("scim/v2/groups/#{group.full_path}/Users/#{identity.extern_uid}?#{params}")
end
it 'responds with 204' do
expect(response).to have_gitlab_http_status(:no_content)
end
end
end end
end end
...@@ -375,12 +363,34 @@ describe API::Scim do ...@@ -375,12 +363,34 @@ describe API::Scim do
describe 'PATCH api/scim/v2/groups/:group/Users/:id' do describe 'PATCH api/scim/v2/groups/:group/Users/:id' do
context 'Remove user' do context 'Remove user' do
shared_examples 'remove user' do
it 'responds with 204' do
expect(response).to have_gitlab_http_status(:no_content)
end
it 'removes the identity link' do it 'removes the identity link' do
expect { identity.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'when path key is present' do
before do
params = { Operations: [{ 'op': 'Replace', 'path': 'active', 'value': 'False' }] }.to_query params = { Operations: [{ 'op': 'Replace', 'path': 'active', 'value': 'False' }] }.to_query
patch scim_api("scim/v2/groups/#{group.full_path}/Users/#{identity.extern_uid}?#{params}") patch scim_api("scim/v2/groups/#{group.full_path}/Users/#{identity.extern_uid}?#{params}")
end
expect { identity.reload }.to raise_error(ActiveRecord::RecordNotFound) it_behaves_like 'remove user'
end
context 'when the path key is not present' do
before do
params = { Operations: [{ 'op': 'replace', 'value': { 'active': false } }] }.to_query
patch scim_api("scim/v2/groups/#{group.full_path}/Users/#{identity.extern_uid}?#{params}")
end
it_behaves_like 'remove user'
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment