Skip to content

Commit a38fc2b

Browse files
committed
Merge branch 'main' into providers/oidc
# Conflicts: # README.md # lib/code0/identities.rb
2 parents e55461b + fc2d248 commit a38fc2b

File tree

15 files changed

+122
-11
lines changed

15 files changed

+122
-11
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ jobs:
1414
strategy:
1515
matrix:
1616
ruby:
17+
- '3.1.2' # using 3.1.2 instead of latest patch as 3.1.2 is the minimum specified in the gemspec
1718
- '3.2.2'
19+
- '3.3.6'
1820

1921
steps:
2022
- uses: actions/checkout@v4

.rubocop.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ require:
33
- rubocop-rspec
44

55
AllCops:
6-
TargetRubyVersion: 3.0.0
6+
TargetRubyVersion: 3.1.2
77
NewCops: enable
88

99
Gemspec/DevelopmentDependencies:
@@ -38,6 +38,9 @@ RSpec/ExampleLength:
3838
Style/Documentation:
3939
Enabled: false
4040

41+
Style/HashSyntax:
42+
EnforcedShorthandSyntax: never
43+
4144
RSpec/MultipleMemoizedHelpers:
4245
Enabled: false
4346
RSpec/MultipleExpectations:

Gemfile.lock

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ PATH
33
specs:
44
code0-identities (0.0.0)
55
httparty (~> 0.22)
6+
ruby-saml (~> 1.17.0)
67

78
GEM
89
remote: https://rubygems.org/
@@ -24,8 +25,14 @@ GEM
2425
json (2.7.2)
2526
language_server-protocol (3.17.0.3)
2627
mini_mime (1.1.5)
28+
mini_portile2 (2.8.8)
2729
multi_xml (0.7.1)
2830
bigdecimal (~> 3.1)
31+
nokogiri (1.16.7)
32+
mini_portile2 (~> 2.8.2)
33+
racc (~> 1.4)
34+
nokogiri (1.16.7-x86_64-linux)
35+
racc (~> 1.4)
2936
parallel (1.25.1)
3037
parser (3.3.4.0)
3138
ast (~> 2.4.1)
@@ -77,6 +84,9 @@ GEM
7784
rubocop-rspec_rails (2.28.3)
7885
rubocop (~> 1.40)
7986
ruby-progressbar (1.13.0)
87+
ruby-saml (1.17.0)
88+
nokogiri (>= 1.13.10)
89+
rexml
8090
strscan (3.1.0)
8191
unicode-display_width (2.5.0)
8292
webmock (3.23.1)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ OAuth:
1111
- Github
1212
- Gitlab
1313
- OIDC / oAuth2
14+
- SAML
1415

1516
## Installation
1617

code0-identities.gemspec

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
1111
spec.summary = "Library to manage external identities"
1212
spec.homepage = "https://github.com/code0-tech/code0-identities"
1313
spec.license = "MIT"
14-
spec.required_ruby_version = ">= 3.0.0"
14+
spec.required_ruby_version = ">= 3.1.2"
1515

1616
spec.metadata["homepage_uri"] = spec.homepage
1717
spec.metadata["source_code_uri"] = spec.homepage
@@ -31,12 +31,13 @@ Gem::Specification.new do |spec|
3131
spec.require_paths = ["lib"]
3232

3333
spec.add_dependency "httparty", "~> 0.22"
34-
spec.add_development_dependency "webmock", "~> 3.23.1"
34+
spec.add_dependency "ruby-saml", "~> 1.17.0"
3535

3636
spec.add_development_dependency "rake", "~> 13.0"
3737
spec.add_development_dependency "rspec", "~> 3.0"
3838
spec.add_development_dependency "rubocop", "~> 1.21"
3939
spec.add_development_dependency "rubocop-rake", "~> 0.6"
4040
spec.add_development_dependency "rubocop-rspec", "~> 2.29" # Uncomment to register a new dependency of your gem
41+
spec.add_development_dependency "webmock", "~> 3.23.1"
4142
spec.metadata["rubygems_mfa_required"] = "true"
4243
end

lib/code0/identities.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require "httparty"
4+
require "onelogin/ruby-saml"
45

56
require_relative "identities/version"
67
require_relative "identities/identity_provider"
@@ -11,6 +12,7 @@
1112
require_relative "identities/provider/discord"
1213
require_relative "identities/provider/github"
1314
require_relative "identities/provider/oidc"
15+
require_relative "identities/provider/saml"
1416

1517
module Code0
1618
module Identities

lib/code0/identities/provider/base_oauth.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,12 @@ def create_identity(response, token, token_type)
6868
end
6969

7070
def config
71-
return config_loader.call if config_loader.is_a?(Proc)
71+
config = config_loader
72+
config = config_loader.call if config_loader.is_a?(Proc)
7273

73-
config_loader
74+
config[:provider_name] ||= self.class.name.downcase.split("::").last
75+
76+
config
7477
end
7578
end
7679
end

lib/code0/identities/provider/discord.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def create_identity(response, *)
3131
username = body["username"]
3232
email = body["email"]
3333

34-
Identity.new(:discord, identifier, username, email, nil, nil)
34+
Identity.new(config[:provider_name], identifier, username, email, nil, nil)
3535
end
3636
end
3737
end

lib/code0/identities/provider/github.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def create_identity(response, access_token, token_type)
4343

4444
email = private_email(access_token, token_type) if email.nil?
4545

46-
Identity.new(:github, identifier, username, email, nil, nil)
46+
Identity.new(config[:provider_name], identifier, username, email, nil, nil)
4747
end
4848
end
4949
end

lib/code0/identities/provider/gitlab.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def create_identity(response, *)
3737
username = body["username"]
3838
email = body["email"]
3939

40-
Identity.new(config_loader.call[:provider_name], identifier, username, email, nil, nil)
40+
Identity.new(config[:provider_name], identifier, username, email, nil, nil)
4141
end
4242
end
4343
end

lib/code0/identities/provider/google.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def create_identity(response, *)
4141
firstname = body["given_name"]
4242
lastname = body["family_name"]
4343

44-
Identity.new(:google, identifier, username, email, firstname, lastname)
44+
Identity.new(config[:provider_name], identifier, username, email, firstname, lastname)
4545
end
4646
end
4747
end

lib/code0/identities/provider/microsoft.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def create_identity(response, *)
3636
lastname = body["familyname"]
3737
email = body["email"]
3838

39-
Identity.new(:microsoft, identifier, nil, email, firstname, lastname)
39+
Identity.new(config[:provider_name], identifier, nil, email, firstname, lastname)
4040
end
4141
end
4242
end

lib/code0/identities/provider/saml.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# frozen_string_literal: true
2+
3+
module Code0
4+
module Identities
5+
module Provider
6+
class Saml
7+
attr_reader :config_loader
8+
9+
def initialize(config_loader)
10+
@config_loader = config_loader
11+
end
12+
13+
def authorization_url
14+
request = OneLogin::RubySaml::Authrequest.new
15+
request.create(create_settings)
16+
17+
request.instance_variable_get :@login_url
18+
end
19+
20+
def load_identity(**params)
21+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse],
22+
{ **config[:response_settings], settings: create_settings })
23+
attributes = response.attributes
24+
25+
Identity.new(config[:provider_name],
26+
response.name_id,
27+
find_attribute(attributes, config[:attribute_statements][:username]),
28+
find_attribute(attributes, config[:attribute_statements][:email]),
29+
find_attribute(attributes, config[:attribute_statements][:firstname]),
30+
find_attribute(attributes, config[:attribute_statements][:lastname]))
31+
end
32+
33+
private
34+
35+
def find_attribute(attributes, attribute_statements)
36+
attribute_statements.each do |statement|
37+
return attributes[statement] unless attributes[statement].nil?
38+
end
39+
nil
40+
end
41+
42+
def create_settings
43+
if config[:metadata_url].nil?
44+
settings = OneLogin::RubySaml::Settings.new
45+
else
46+
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
47+
settings = idp_metadata_parser.parse_remote(config[:metadata_url])
48+
end
49+
50+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
51+
52+
config[:settings].each do |key, value|
53+
settings.send(:"#{key}=", value)
54+
end
55+
settings
56+
end
57+
58+
def config
59+
config = config_loader
60+
config = config_loader.call if config_loader.is_a?(Proc)
61+
62+
# rubocop:disable Layout/LineLength
63+
config[:provider_name] ||= :saml
64+
config[:response_settings] ||= {}
65+
config[:settings] ||= {}
66+
config[:attribute_statements] ||= {}
67+
config[:attribute_statements][:username] ||= %w[username name http://schemas.goauthentik.io/2021/02/saml/username]
68+
config[:attribute_statements][:email] ||= %w[email mail http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress http://schemas.microsoft.com/ws/2008/06/identity/claims/emailaddress]
69+
config[:attribute_statements][:firstname] ||= %w[first_name firstname firstName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.microsoft.com/ws/2008/06/identity/claims/givenname]
70+
config[:attribute_statements][:lastname] ||= %w[last_name lastname lastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname http://schemas.microsoft.com/ws/2008/06/identity/claims/surname]
71+
# rubocop:enable Layout/LineLength
72+
73+
config
74+
end
75+
end
76+
end
77+
end
78+
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module Code0
2+
module Identities
3+
module Provider
4+
class Saml
5+
def authorization_url: () -> String
6+
7+
def load_identity: (Hash[Symbol, any]) -> Identity
8+
end
9+
end
10+
end
11+
end

spec/code0/identities/provider/discord_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
.to_return(body: response_body, headers: { "Content-Type": "application/json" })
5959

6060
expect(service_response.identifier).to eq(1)
61-
expect(service_response.provider).to eq(:discord)
61+
expect(service_response.provider).to eq("discord")
6262
expect(service_response.username).to eq("name")
6363
expect(service_response.email).to eq("example@code0.tech")
6464
end

0 commit comments

Comments
 (0)