omniauth-himari 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2a1280e30d7d894b2db9ed7f9bbc12ddc2feb23c05b8b7e2114b0346ae314966
4
+ data.tar.gz: e9d350f407d5de7c26a72eaef8886935abb0334da60249e7137fd594af3a4eab
5
+ SHA512:
6
+ metadata.gz: 978c7555d9221b256309c56019a85e3030151627fc3f865d70c72976ebfe8008f4f2ae8d66e998e49eb8cbb0f955f794f38ebec7135dcc5d41cdda7b7c849f31
7
+ data.tar.gz: 82129a5f925165b1cf4edabfc0882d900acfbd81dad4da6bb6066b7edca7c94e9cdc4f163dfbd766542095e307d0812b1f515cb31416af21fbe899e1e39b3079
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-03-24
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Sorah Fukumori
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # OmniAuth strategy for Himari
2
+
3
+ OmniAuth strategy to act as OIDC RP and use [Himari](https://github.com/sorah/himari) for OP.
4
+
5
+ ## Installation
6
+
7
+ ```ruby
8
+ # Gemfile
9
+ gem 'omniauth-himari'
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ### Setup
15
+
16
+ ```ruby
17
+ use OmniAuth::Builder do
18
+ provider :himari, {
19
+ site: 'https://himari.example.invalid',
20
+ client_id: '...',
21
+ client_secret: '...',
22
+
23
+ # verify_options: { ... } # JWT.decode verify options override
24
+ # verify_at_hash: true, # Verify at_hash returned in ID token
25
+
26
+ # use_userinfo: false # force use of userinfo endpoint for raw_info
27
+ # jwks_url: '...' # JWKs url to override (default=/public/jwks)
28
+
29
+ # user_agent: '...' # user-agent to send (default=OmniAuthHimari/X.Y.Z)
30
+
31
+ ## omniauth-oauth2 common strategy options
32
+ # client_options: { ... },
33
+ # pkce: true,
34
+ }
35
+ end
36
+ ```
37
+
38
+ ### Auth Hash
39
+
40
+ ```json
41
+ {
42
+ "provider": "himari",
43
+ "uid": "id_claim.sub",
44
+ "info": {
45
+ "name": "name || sub",
46
+ "nickname": "preferred_username",
47
+ "email": "email",
48
+ "first_name": "given_name",
49
+ "last_name": "family_name",
50
+ "image": "picture"
51
+ },
52
+ "credentials": {
53
+ "token": "access_token",
54
+ "expires_at": 42,
55
+ "expires": true,
56
+ "id_token": "id_token"
57
+ },
58
+ "extra": {
59
+ "userinfo_used": false,
60
+ "id_token": {
61
+ "claims": {
62
+ "sub": "sub",
63
+ "name": "name",
64
+ "preferred_username": "preferred_username",
65
+ "iss": "https://himari.example.invalid",
66
+ "aud": "...",
67
+ "iat": 1679595201,
68
+ "nbf": 1679595201,
69
+ "exp": 1679598801,
70
+ "at_hash": "..."
71
+ },
72
+ "header": {
73
+ "typ": "JWT",
74
+ "alg": "RS256",
75
+ "kid": "..."
76
+ }
77
+ },
78
+ "raw_info": {
79
+ "sub": "sub",
80
+ "name": "name",
81
+ "preferred_username": "preferred_username",
82
+ "iss": "https://himari.example.invalid",
83
+ "aud": "...",
84
+ "iat": 1679595201,
85
+ "nbf": 1679595201,
86
+ "exp": 1679598801,
87
+ "at_hash": "..."
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+
94
+ ## Development
95
+
96
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
97
+
98
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
99
+
100
+ ## Contributing
101
+
102
+ Bug reports and pull requests are welcome on GitHub at https://github.com/sorah/himari.
103
+
104
+ ## License
105
+
106
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ Bundler::GemHelper.tag_prefix = "omniauth-himari/"
9
+
10
+ task default: :spec
@@ -0,0 +1,177 @@
1
+ require 'omniauth'
2
+ require 'omniauth-oauth2'
3
+ require 'oauth2'
4
+ require 'digest/sha2'
5
+
6
+ require 'omniauth-himari/version'
7
+
8
+ module OmniAuth
9
+ module Strategies
10
+ class Himari < OmniAuth::Strategies::OAuth2
11
+ class IdTokenMissing < StandardError; end
12
+ class ConfigurationError < StandardError; end
13
+ class VerificationError < StandardError; end
14
+
15
+ option :name, 'himari'
16
+
17
+ option :client_options, {}
18
+ option :pkce, true
19
+
20
+ option :verify_options, {}
21
+ option :verify_at_hash, true
22
+
23
+ option :use_userinfo, false
24
+
25
+ option :jwks_url, nil
26
+
27
+ option :user_agent, nil
28
+
29
+ args %i(site)
30
+
31
+ def client
32
+ options.client_options.site ||= options.site
33
+ options.client_options.authorize_url ||= '/oidc/authorize'
34
+ options.client_options.token_url ||= '/public/oidc/token'
35
+ options.client_options.userinfo_url ||= '/public/oidc/userinfo'
36
+ options.client_options.access_token_class ||= AccessToken # https://gitlab.com/oauth-xx/oauth2/-/issues/628
37
+
38
+ options.client_options.connection_opts ||= {}
39
+ options.client_options.connection_opts[:headers] ||= {}
40
+ options.client_options.connection_opts[:headers] = {
41
+ 'User-Agent' => user_agent,
42
+ }.merge(options.client_options.connection_opts[:headers])
43
+
44
+ raise ConfigurationError, "client_id and client_secret is required" unless options.client_id && options.client_secret
45
+ raise ConfigurationError, "site is required" unless options.client_options.site
46
+ super
47
+ end
48
+
49
+ uid { raw_info['sub'] }
50
+
51
+ credentials do
52
+ retval = {
53
+ 'token' => access_token.token,
54
+ 'expires' => access_token.expires?,
55
+ 'expires_at' => access_token.expires_at,
56
+ 'id_token' => access_token.params && access_token.params['id_token'],
57
+ }
58
+ raise IdTokenMissing, 'id_token is missing' unless retval['id_token']
59
+ retval
60
+ end
61
+
62
+ info do
63
+ {
64
+ name: raw_info['name'] || raw_info['sub'],
65
+ nickname: raw_info['preferred_username'],
66
+ email: raw_info['email'],
67
+ first_name: raw_info['given_name'],
68
+ last_name: raw_info['family_name'],
69
+ image: raw_info['picture'],
70
+ }
71
+ end
72
+
73
+ extra do
74
+ {
75
+ userinfo_used: options.use_userinfo,
76
+ id_token: id_token.to_h,
77
+ raw_info: raw_info,
78
+ }
79
+ end
80
+
81
+ def verify_at_hash!(id_token)
82
+ return unless options.verify_at_hash
83
+
84
+ function = case id_token.header['alg'] # this is safe as we've verified
85
+ when 'ES256', 'RS256'; Digest::SHA256
86
+ when 'ES384'; Digest::SHA384
87
+ when 'ES512'; Digest::SHA512
88
+ else
89
+ raise VerificationError, "unknown hash function to verify at_hash for #{id_token.header['alg']}"
90
+ end
91
+
92
+ dgst = function.digest(access_token.token)
93
+ expected_at_hash = Base64.urlsafe_encode64(dgst[0, dgst.size/2], padding: false)
94
+
95
+ given_at_hash = id_token.claims['at_hash']
96
+
97
+ unless given_at_hash == expected_at_hash
98
+ raise VerificationError, "at_hash mismatch #{given_at_hash.inspect}, #{expected_at_hash.inspect}"
99
+ end
100
+ end
101
+
102
+ def raw_info
103
+ @raw_info ||= (!skip_info? && options.use_userinfo) ? access_token.get('/public/oidc/userinfo').parsed : id_token.claims
104
+ end
105
+
106
+ def faraday
107
+ @faraday ||= Faraday.new(options.site, headers: {'User-Agent' => user_agent}) do |b|
108
+ b.response :json
109
+ b.response :raise_error
110
+ end
111
+ end
112
+
113
+ def callback_url
114
+ options[:redirect_uri] || (full_host + callback_path) # https://github.com/omniauth/omniauth-oauth2/pull/142
115
+ end
116
+
117
+ def user_agent
118
+ options.user_agent || "OmniauthHimari/#{Omniauth::Himari::VERSION}"
119
+ end
120
+
121
+ def authorize_params
122
+ super.tap do |params|
123
+ params[:scope] = 'openid'
124
+ end
125
+ end
126
+
127
+ IdToken = Struct.new(:claims, :header)
128
+
129
+ def id_token
130
+ @id_token ||= begin
131
+ jwt = access_token.params['id_token'] or raise(IdTokenMissing, 'id_token is missing')
132
+ retval = IdToken.new(*JWT.decode(
133
+ jwt,
134
+ nil,
135
+ true,
136
+ {
137
+ algorithms: jwks.map { |k| k[:alg] }.compact.uniq,
138
+ jwks: jwks,
139
+ verify_aud: true,
140
+ aud: options.client_id,
141
+ verify_iss: true,
142
+ iss: options.site,
143
+ verify_expiration: true,
144
+ }.merge(options.verify_options)
145
+ ))
146
+ verify_at_hash!(retval)
147
+ retval
148
+ end
149
+ end
150
+
151
+ def jwks
152
+ JWT::JWK::Set.new(jwks_json).tap do |set|
153
+ set.filter! { |k| k[:use] == 'sig' }
154
+ end
155
+ end
156
+
157
+ def jwks_json
158
+ faraday.get(options.jwks_url || 'public/jwks').body
159
+ rescue Faraday::Error => e
160
+ raise JwksUnavailable, "failed to retrieve jwks; #{e.inspect}"
161
+ end
162
+
163
+ # https://github.com/omniauth/omniauth-oauth2/pull/142
164
+ class AccessToken < ::OAuth2::AccessToken
165
+ private def extra_tokens_warning(*)
166
+ # do nothing
167
+ end
168
+
169
+ def inspect
170
+ "#<#{self.class.name}:0x#{self.__id__.to_s(16)}>"
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omniauth
4
+ module Himari
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ require 'omniauth-himari/version'
2
+ require 'omniauth/strategies/himari'
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/omniauth-himari/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "omniauth-himari"
7
+ spec.version = Omniauth::Himari::VERSION
8
+ spec.authors = ["Sorah Fukumori"]
9
+ spec.email = ["her@sorah.jp"]
10
+
11
+ spec.summary = "OmniAuth strategy for Himari"
12
+ spec.description = "OmniAuth strategy to act as OIDC RP and use [Himari](https://github.com/sorah/himari) for OP."
13
+ spec.homepage = "https://github.com/sorah/himari"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.7.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/sorah/himari"
19
+ #spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ if ENV['HIMARI_LAMBDA_IMAGE']
24
+ spec.files = Dir.chdir(__dir__) { Dir["./**/*"] }.reject { |f| (File.expand_path(f) == __FILE__) }
25
+ else
26
+ spec.files = Dir.chdir(__dir__) do
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
29
+ end
30
+ end
31
+ end
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ["lib"]
35
+
36
+ spec.add_dependency 'omniauth'
37
+ spec.add_dependency 'omniauth-oauth2'
38
+ spec.add_dependency 'oauth2'
39
+ spec.add_dependency 'faraday'
40
+
41
+ # Uncomment to register a new dependency of your gem
42
+ # spec.add_dependency "example-gem", "~> 1.0"
43
+
44
+ # For more information and examples about making a new gem, check out our
45
+ # guide at: https://bundler.io/guides/creating_gem.html
46
+ end
@@ -0,0 +1,6 @@
1
+ module Omniauth
2
+ module Himari
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omniauth-himari
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sorah Fukumori
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-03-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: omniauth
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: omniauth-oauth2
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: oauth2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: OmniAuth strategy to act as OIDC RP and use [Himari](https://github.com/sorah/himari)
70
+ for OP.
71
+ email:
72
+ - her@sorah.jp
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".rspec"
78
+ - CHANGELOG.md
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - lib/omniauth-himari.rb
83
+ - lib/omniauth-himari/version.rb
84
+ - lib/omniauth/strategies/himari.rb
85
+ - omniauth-himari.gemspec
86
+ - sig/omniauth/himari.rbs
87
+ homepage: https://github.com/sorah/himari
88
+ licenses:
89
+ - MIT
90
+ metadata:
91
+ homepage_uri: https://github.com/sorah/himari
92
+ source_code_uri: https://github.com/sorah/himari
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: 2.7.0
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubygems_version: 3.1.6
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: OmniAuth strategy for Himari
112
+ test_files: []