Commit 755ce3e9 authored by Drew Blessing's avatar Drew Blessing Committed by Drew Blessing

Allow SAML response to set certain user attributes on creation

Allow SAML to set user attributes via additional attribute
statements in the SAML response. These allowed attributes are
currently limited to `can_create_group` and `projects_limit`.
parent 1b962786
......@@ -220,6 +220,46 @@ On subsequent visits, you should be able to go [sign in to GitLab.com with SAML]
1. From the list of apps, click on the "GitLab.com" app (The name is set by the administrator of the identity provider).
1. You are then signed in to GitLab.com and redirected to the group.
### Configure user settings from SAML response
GitLab allows setting certain user attributes based on values from the SAML response.
This affects users created on first sign-in via Group SAML. Existing users'
attributes are not affected regardless of the values sent in the SAML response.
#### Supported user attributes
- `can_create_group` - 'true' or 'false' to indicate whether the user can create
new groups. Default is `true`.
- `projects_limit` - The total number of personal projects a user can create.
A value of `0` means the user cannot create new projects in their personal
namespace. Default is `10000`.
#### Example SAML response
You can find SAML responses in the developer tools or console of your browser,
in base64-encoded format. Use the base64 decoding tool of your choice to
convert the information to XML. An example SAML response is shown here.
```xml
<saml2:AttributeStatement>
<saml2:Attribute Name="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user.email</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="first_name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
<saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user.firstName</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="last_name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
<saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user.lastName</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="can_create_group" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
<saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">true</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="projects_limit" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
<saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">10</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
```
### Role
Starting from [GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/issues/214523), group owners can set a 'Default membership role' other than 'Guest'. To do so, [configure the SAML SSO for the group](#configuring-gitlab). That role becomes the starting access level of all users added to the group.
......
---
title: Allow SAML response to set certain user attributes on creation
merge_request: 49394
author:
type: added
......@@ -4,9 +4,17 @@ module Gitlab
module Auth
module GroupSaml
class AuthHash < Gitlab::Auth::Saml::AuthHash
ALLOWED_USER_ATTRIBUTES = %w(can_create_group projects_limit).freeze
def groups
Array.wrap(get_raw('groups') || get_raw('Groups'))
end
ALLOWED_USER_ATTRIBUTES.each do |attribute|
define_method(attribute) do
Array(get_raw(attribute)).first
end
end
end
end
end
......
......@@ -51,6 +51,14 @@ module Gitlab
def build_new_user(skip_confirmation: false)
super.tap do |user|
user.provisioned_by_group_id = saml_provider.group_id
# rubocop:disable GitlabSecurity/PublicSend
AuthHash::ALLOWED_USER_ATTRIBUTES.each do |attribute|
next unless value = auth_hash.public_send(attribute)
user.public_send("#{attribute}=", value)
end
# rubocop:enable GitlabSecurity/PublicSend
end
end
......
......@@ -35,4 +35,42 @@ RSpec.describe Gitlab::Auth::GroupSaml::AuthHash do
end
end
end
describe 'allowed user attributes methods' do
context 'when the attributes are presented as an array' do
let(:raw_info_attr) { { 'can_create_group' => %w(true), 'projects_limit' => %w(20) } }
it 'returns the proper can_create_groups value' do
expect(saml_auth_hash.can_create_group).to eq "true"
end
it 'returns the proper projects_limit value' do
expect(saml_auth_hash.projects_limit).to eq "20"
end
end
context 'when the attributes are presented as a string' do
let(:raw_info_attr) { { 'can_create_group' => 'false', 'projects_limit' => '20' } }
it 'returns the proper can_create_groups value' do
expect(saml_auth_hash.can_create_group).to eq "false"
end
it 'returns the proper projects_limit value' do
expect(saml_auth_hash.projects_limit).to eq "20"
end
end
context 'when the attributes are not present in the SAML response' do
let(:raw_info_attr) { {} }
it 'returns nil for can_create_group' do
expect(saml_auth_hash.can_create_group).to eq nil
end
it 'returns nil for can_create_groups' do
expect(saml_auth_hash.projects_limit).to eq nil
end
end
end
end
......@@ -7,7 +7,7 @@ RSpec.describe Gitlab::Auth::GroupSaml::User do
let(:uid) { 1234 }
let(:saml_provider) { create(:saml_provider) }
let(:group) { saml_provider.group }
let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: 'group_saml', info: info_hash) }
let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: 'group_saml', info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new }) }
let(:info_hash) do
{
name: generate(:name),
......@@ -82,6 +82,23 @@ RSpec.describe Gitlab::Auth::GroupSaml::User do
it 'creates the user SAML identity' do
expect { find_and_update }.to change { Identity.count }.by(1)
end
context 'when user attributes are present' do
before do
auth_hash[:extra][:raw_info] =
OneLogin::RubySaml::Attributes.new(
'can_create_group' => %w(true), 'projects_limit' => %w(20)
)
end
it 'creates the user with correct can_create_group attribute' do
expect(find_and_update.can_create_group).to eq(true)
end
it 'creates the user with correct projects_limit attribute' do
expect(find_and_update.projects_limit).to eq(20)
end
end
end
context 'when a conflicting user already exists' do
......
......@@ -138,13 +138,15 @@ module LoginHelpers
secret: 'mock_secret'
},
extra: {
raw_info: {
info: {
name: 'mockuser',
email: email,
image: 'mock_user_thumbnail_url'
raw_info: OneLogin::RubySaml::Attributes.new(
{
info: {
name: 'mockuser',
email: email,
image: 'mock_user_thumbnail_url'
}
}
},
),
response_object: response_object
}
}).merge(additional_info) { |_, old_hash, new_hash| old_hash.merge(new_hash) }
......
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