omniauth-doximity-oauth2 0.1.0 → 1.0.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 +4 -4
- data/.circleci/config.yml +134 -0
- data/.github/CODEOWNERS +2 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +17 -0
- data/CHANGELOG.md +18 -0
- data/CONTRIBUTING.md +31 -0
- data/CONTRIBUTORS.md +4 -0
- data/Gemfile +1 -3
- data/Gemfile.lock +112 -0
- data/LICENSE.txt +201 -0
- data/README.md +127 -13
- data/Rakefile +21 -2
- data/lib/omniauth/strategies/doximity_oauth2.rb +135 -0
- data/lib/omniauth-doximity-oauth2/errors.rb +28 -0
- data/lib/omniauth-doximity-oauth2/version.rb +7 -0
- data/lib/omniauth-doximity-oauth2.rb +4 -0
- data/omniauth-doximity-oauth2.gemspec +40 -0
- data/spec/omniauth/strategies/doximity_oauth2_spec.rb +130 -0
- data/spec/rubocop_spec.rb +9 -0
- data/spec/spec_helper.rb +4 -0
- data/tasks/ci.rake +16 -0
- data/vendor/cache/activesupport-6.1.5.1.gem +0 -0
- data/vendor/cache/ast-2.4.2.gem +0 -0
- data/vendor/cache/concurrent-ruby-1.1.10.gem +0 -0
- data/vendor/cache/diff-lcs-1.5.0.gem +0 -0
- data/vendor/cache/faraday-2.2.0.gem +0 -0
- data/vendor/cache/faraday-net_http-2.0.2.gem +0 -0
- data/vendor/cache/hashie-5.0.0.gem +0 -0
- data/vendor/cache/i18n-1.10.0.gem +0 -0
- data/vendor/cache/jwt-2.3.0.gem +0 -0
- data/vendor/cache/minitest-5.15.0.gem +0 -0
- data/vendor/cache/multi_json-1.15.0.gem +0 -0
- data/vendor/cache/multi_xml-0.6.0.gem +0 -0
- data/vendor/cache/oauth2-1.4.9.gem +0 -0
- data/vendor/cache/omniauth-2.1.0.gem +0 -0
- data/vendor/cache/omniauth-oauth2-1.7.2.gem +0 -0
- data/vendor/cache/openssl-3.0.0.gem +0 -0
- data/vendor/cache/parallel-1.22.1.gem +0 -0
- data/vendor/cache/parser-3.1.2.0.gem +0 -0
- data/vendor/cache/rack-2.2.3.gem +0 -0
- data/vendor/cache/rack-protection-2.2.0.gem +0 -0
- data/vendor/cache/rainbow-3.1.1.gem +0 -0
- data/vendor/cache/rake-13.0.6.gem +0 -0
- data/vendor/cache/rdoc-6.3.3.gem +0 -0
- data/vendor/cache/regexp_parser-2.3.1.gem +0 -0
- data/vendor/cache/rexml-3.2.5.gem +0 -0
- data/vendor/cache/rspec-3.11.0.gem +0 -0
- data/vendor/cache/rspec-core-3.11.0.gem +0 -0
- data/vendor/cache/rspec-expectations-3.11.0.gem +0 -0
- data/vendor/cache/rspec-mocks-3.11.0.gem +0 -0
- data/vendor/cache/rspec-support-3.11.0.gem +0 -0
- data/vendor/cache/rubocop-1.28.2.gem +0 -0
- data/vendor/cache/rubocop-ast-1.17.0.gem +0 -0
- data/vendor/cache/rubocop-rails-2.14.2.gem +0 -0
- data/vendor/cache/ruby-progressbar-1.11.0.gem +0 -0
- data/vendor/cache/ruby2_keywords-0.0.5.gem +0 -0
- data/vendor/cache/sdoc-2.3.1.gem +0 -0
- data/vendor/cache/tzinfo-2.0.4.gem +0 -0
- data/vendor/cache/unicode-display_width-2.1.0.gem +0 -0
- data/vendor/cache/zeitwerk-2.5.4.gem +0 -0
- metadata +249 -21
- data/lib/omniauth/doximity/oauth2/version.rb +0 -9
- data/lib/omniauth/doximity/oauth2.rb +0 -12
- data/sig/omniauth/doximity/oauth2.rbs +0 -8
data/README.md
CHANGED
@@ -1,29 +1,143 @@
|
|
1
|
-
# Omniauth::
|
1
|
+
# Omniauth::DoximityOauth2
|
2
2
|
|
3
|
-
|
3
|
+
OmniAuth strategy for Doximity.
|
4
4
|
|
5
|
-
|
5
|
+
Sign up for Doximity's API to get your OAuth credentials at: https://www.doximity.com/developers/api_signup
|
6
6
|
|
7
|
-
|
7
|
+
For more details on what tools we have available, read our developer docs: https://www.doximity.com/developers/documentation
|
8
8
|
|
9
|
-
|
9
|
+
## Installation
|
10
10
|
|
11
|
-
|
11
|
+
Add to your `Gemfile`:
|
12
12
|
|
13
|
-
|
13
|
+
```ruby
|
14
|
+
gem 'omniauth-doximity-oauth2'
|
15
|
+
```
|
14
16
|
|
15
|
-
|
17
|
+
Then `bundle install`.
|
16
18
|
|
17
19
|
## Usage
|
18
20
|
|
19
|
-
|
21
|
+
Here's an example for adding the middleware to a Rails app in `config/initializers/omniauth.rb`:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
DOXIMITY_OMNIAUTH_SETUP = lambda do |env|
|
25
|
+
env['omniauth.strategy'].options[:client_id] = ENV["DOXIMITY_CLIENT_ID"]
|
26
|
+
env['omniauth.strategy'].options[:client_secret] = ENV["DOXIMITY_CLIENT_SECRET"]
|
27
|
+
env['omniauth.strategy'].options[:scope] = "openid profile:read:basic profile:read:email"
|
28
|
+
end
|
29
|
+
|
30
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
31
|
+
configure do |config|
|
32
|
+
config.path_prefix = '/auth'
|
33
|
+
end
|
34
|
+
provider :doximity_oauth2, setup: DOXIMITY_OMNIAUTH_SETUP
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
Talk with the Doximity API team about what scopes you need for your application, and make sure to edit your OmniAuth initializer to request them.
|
39
|
+
|
40
|
+
Update your `config/routes.rb` to support Doximity OmniAuth callbacks on your session controller:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
Rails.application.routes.draw do
|
44
|
+
get "/auth/:provider/callback" => "sessions#create"
|
45
|
+
post "/signout" => "sessions#destroy"
|
46
|
+
get "/auth/failure" => "sessions#failure"
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
Then, create a sign-in button that posts to `/auth/doximity`. Use one of the Sign in with Doximity logos, available here: https://www.doximity.com/developers/documentation#logos-for-use-by-third-party-developers
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
<%= link_to "Sign in with Doximity", "/auth/doximity", method: :post do %>
|
54
|
+
<%= image_tag "https://assets.doxcdn.com/image/upload/v1/apps/doximity/api/api-button-sign-in-with-doximity.png", alt: "Sign in with Doximity button"%>
|
55
|
+
<% end %>
|
56
|
+
```
|
57
|
+
|
58
|
+
Note that in OmniAuth versions 2 and above, links to sign in should use the POST method. Read more [here](https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284)
|
59
|
+
|
60
|
+
In your callback controller, you will have a few resources available to you after the user approves your application and logs in.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
class SessionsController < ApplicationController
|
64
|
+
def create
|
65
|
+
session[:user_uuid] = request.env["omniauth.auth"]["uid"]
|
66
|
+
redirect_to request.env["omniauth.origin"] || "/", :notice => "Signed in!"
|
67
|
+
end
|
20
68
|
|
21
|
-
|
69
|
+
def destroy
|
70
|
+
session.delete(:user_uuid)
|
71
|
+
redirect_to "/"
|
72
|
+
end
|
22
73
|
|
23
|
-
|
74
|
+
def failure
|
75
|
+
redirect_to request.env["omniauth.origin"] || "/", :alert => "Authentication error: #{params[:message].humanize}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
```
|
24
79
|
|
25
|
-
|
80
|
+
You can also add an `origin` param to your `/auth/doximity` post, which will be provided in the `request.env["omniauth.origin"]` variable after the success or failure callback.
|
81
|
+
|
82
|
+
## Configuration
|
83
|
+
|
84
|
+
You can configure several options, inside the configuration lambda:
|
85
|
+
|
86
|
+
* `[:scope]`: A comma-separated list of permissions you want to request from the user.Caveats:
|
87
|
+
* The `openid` scope is suggested. Alternatively, if the `openid` scope is not requested `omniauth-doximity` will make an additional request to retrieve information about the signed in user using your other scopes. Your app may be subject to rate limiting depending on your usage.
|
88
|
+
* Without any scopes, you will still be able to log in the user and retrieve a unique UUIDv4 to distinguish them from other users.
|
89
|
+
|
90
|
+
* `[:name]`: The name of the strategy. The default name is `doximity` but it can be changed to any string. The `:provider` part of OmniAuth URLs will also change to `/auth/{{ name }}`.
|
91
|
+
|
92
|
+
* `[:client_options][:site]`: Override the Doximity OAuth provider website. You may be provided with a development site to use while setting up your integration, which you would set here.
|
93
|
+
|
94
|
+
* `[:pkce]`: A boolean denoting whether to follow the PKCE OAuth spec. Default `true`. Note that if set to false, your OmniAuth credentials hash will not include a `refresh` token. Your OAuth application also may require PKCE to use OmniAuth.
|
95
|
+
|
96
|
+
## Auth Hash
|
97
|
+
|
98
|
+
Here's an example of an authentication hash available in the callback by accessing `request.env['omniauth.auth']`:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
{
|
102
|
+
"provider" => "doximity",
|
103
|
+
"uid" => "cc485bd2-b25a-4677-b05c-e98febf7789d",
|
104
|
+
"info" => {
|
105
|
+
"name" => "Test User",
|
106
|
+
"given_name" => "Test",
|
107
|
+
"family_name" => "User",
|
108
|
+
"primary_email" => "md@doximity.com",
|
109
|
+
"emails" => ["md@doximity.com"],
|
110
|
+
"profile_photo_url" => "http://res.cloudinary.com/doximity-development/image/upload/l_text:Helvetica_130_bold:AT,co_rgb:FFFFFF,t_profile_photo_320x320/profile-placeholder-registered-5.jpg",
|
111
|
+
"credentials" => "Other",
|
112
|
+
"specialty" => "Optometrist"
|
113
|
+
},
|
114
|
+
"credentials" => {
|
115
|
+
"token" => "gMej-ecC9Wzy4KkUCypYQ1J_8mQ1Yo9RXJYwU2kCyPKciuuOIxHflFlLP0PLlJmwnjPwlNa7nkQeeOcz-zyC6w==",
|
116
|
+
"refresh_token" => "go-40T6xPOzSOd09NTElQ0tGi-BU5hluljET8wa3syzxBqsG5BP0PJW_CsbDhmm49T081jhsIMnP-OQG8McYYPdOENc027K87gGSurOquANzx8qlo4hTJ903LNGpTZ6VcV1Ci0jomvJdH1NsCq5nLxeCy4dBctTZEMA-c3pOVZ0=",
|
117
|
+
"expires_at" => 1650335410,
|
118
|
+
"expires" => true,
|
119
|
+
"access_token" => "gMej-ecC9Wzy4KkUCypYQ1J_8mQ1Yo9RXJYwU2kCyPKciuuOIxHflFlLP0PLlJmwnjPwlNa7nkQeeOcz-zyC6w==",
|
120
|
+
"scope" => "profile:read:email profile:read:basic openid",
|
121
|
+
"token_type" => "bearer"
|
122
|
+
}, "extra" => {
|
123
|
+
"raw_subject_info" => {
|
124
|
+
"acr" => 2, "at_hash" => "uVfpy56HzI3J_dZR2kyxrQ", "aud" => ["https://auth.doximity.com", "6bd7e37e80fd06819ca13b268adea5fbe57446a9f9e1982f9483813d7272acf1"], "auth_time" => 1650333376, "azp" => "6bd7e37e80fd06819ca13b268adea5fbe57446a9f9e1982f9483813d7272acf1", "credentials" => "Other", "emails" => ["md@doximity.com"], "exp" => 1650335384, "family_name" => "User", "given_name" => "Test", "iat" => 1650333610, "iss" => "https://auth.doximity.com", "name" => "Test User", "primary_email" => "md@doximity.com", "profile_photo_url" => "http://res.cloudinary.com/doximity-development/image/upload/l_text:Helvetica_130_bold:AT,co_rgb:FFFFFF,t_profile_photo_320x320/profile-placeholder-registered-5.jpg", "sid" => "9", "specialty" => "Optometrist", "sub" => "cc485bd2-b25a-4677-b05c-e98febf7789d"
|
125
|
+
}, "raw_credential_info" => {
|
126
|
+
"token_type" => "bearer", "scope" => "profile:read:email profile:read:basic openid", "id_token" => "{{JWT omitted for brevity}}", "access_token" => "gMej-ecC9Wzy4KkUCypYQ1J_8mQ1Yo9RXJYwU2kCyPKciuuOIxHflFlLP0PLlJmwnjPwlNa7nkQeeOcz-zyC6w==", "refresh_token" => "go-40T6xPOzSOd09NTElQ0tGi-BU5hluljET8wa3syzxBqsG5BP0PJW_CsbDhmm49T081jhsIMnP-OQG8McYYPdOENc027K87gGSurOquANzx8qlo4hTJ903LNGpTZ6VcV1Ci0jomvJdH1NsCq5nLxeCy4dBctTZEMA-c3pOVZ0=", "expires_at" => 1650335410
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
```
|
26
131
|
|
27
132
|
## Contributing
|
28
133
|
|
29
|
-
|
134
|
+
1. Fork it
|
135
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
136
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
137
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
138
|
+
5. Create a new Pull Request
|
139
|
+
6. Sign the CLA if you haven't yet. See [CONTRIBUTING.md](./CONTRIBUTING.md)
|
140
|
+
|
141
|
+
## License
|
142
|
+
|
143
|
+
The gem is licensed under an Apache 2 license. Contributors are required to sign a contributor license agreement. See [LICENSE.txt](./LICENSE.txt) and [CONTRIBUTING.md](./CONTRIBUTING.md) for more information.
|
data/Rakefile
CHANGED
@@ -1,4 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "bundler
|
4
|
-
|
3
|
+
require File.join("bundler", "gem_tasks")
|
4
|
+
require File.join("rspec", "core", "rake_task")
|
5
|
+
require "sdoc"
|
6
|
+
|
7
|
+
FileList["tasks/*.rake"].each { |task| load task }
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
|
+
|
11
|
+
task default: :spec
|
12
|
+
|
13
|
+
RDoc::Task.new do |rdoc|
|
14
|
+
rdoc.main = "README.md"
|
15
|
+
rdoc.markup = "tomdoc"
|
16
|
+
rdoc.options << "--format=sdoc"
|
17
|
+
rdoc.options << "--github --encoding=UTF-8"
|
18
|
+
rdoc.rdoc_dir = "doc"
|
19
|
+
rdoc.rdoc_files.exclude("vendor", "tmp")
|
20
|
+
rdoc.rdoc_files.include("README.md", "lib", "*.rb")
|
21
|
+
rdoc.template = "rails"
|
22
|
+
rdoc.title = "omniauth-doximity Documentation"
|
23
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "omniauth/strategies/oauth2"
|
4
|
+
require "omniauth-doximity-oauth2/errors"
|
5
|
+
require "active_support/core_ext/hash/indifferent_access"
|
6
|
+
require "uri"
|
7
|
+
require "rack/utils"
|
8
|
+
require "jwt"
|
9
|
+
require "faraday"
|
10
|
+
require "multi_json"
|
11
|
+
|
12
|
+
module OmniAuth
|
13
|
+
module Strategies
|
14
|
+
# Doximity OmniAuth strategy.
|
15
|
+
class DoximityOauth2 < OmniAuth::Strategies::OAuth2
|
16
|
+
DEFAULT_SCOPE = "openid profile:read:basic"
|
17
|
+
|
18
|
+
option :name, "doximity"
|
19
|
+
|
20
|
+
option :pkce, true
|
21
|
+
|
22
|
+
option :authorize_options, [:scope]
|
23
|
+
|
24
|
+
option :client_options, {
|
25
|
+
site: "https://auth.doximity.com",
|
26
|
+
authorize_url: "/oauth/authorize",
|
27
|
+
token_url: "/oauth/token",
|
28
|
+
jwks_url: "/.well-known/jwks.json"
|
29
|
+
}
|
30
|
+
|
31
|
+
option :auth_token_params, {
|
32
|
+
mode: :header
|
33
|
+
}
|
34
|
+
|
35
|
+
uid { raw_subject_info["sub"] }
|
36
|
+
|
37
|
+
info do
|
38
|
+
prune({
|
39
|
+
name: raw_subject_info["name"],
|
40
|
+
given_name: raw_subject_info["given_name"],
|
41
|
+
middle_name: raw_subject_info["middle_name"],
|
42
|
+
family_name: raw_subject_info["family_name"],
|
43
|
+
primary_email: raw_subject_info["primary_email"],
|
44
|
+
emails: raw_subject_info["emails"],
|
45
|
+
profile_photo_url: raw_subject_info["profile_photo_url"],
|
46
|
+
credentials: raw_subject_info["credentials"],
|
47
|
+
specialty: raw_subject_info["specialty"],
|
48
|
+
permissions: raw_subject_info["permissions"]
|
49
|
+
})
|
50
|
+
end
|
51
|
+
|
52
|
+
extra do
|
53
|
+
prune({
|
54
|
+
raw_subject_info: raw_subject_info,
|
55
|
+
raw_credential_info: raw_credential_info
|
56
|
+
})
|
57
|
+
end
|
58
|
+
|
59
|
+
credentials do
|
60
|
+
prune({
|
61
|
+
access_token: raw_credential_info["access_token"],
|
62
|
+
refresh_token: raw_credential_info["refresh_token"],
|
63
|
+
expires_at: raw_credential_info["expires_at"],
|
64
|
+
scope: raw_credential_info["scope"],
|
65
|
+
token_type: raw_credential_info["token_type"]
|
66
|
+
})
|
67
|
+
end
|
68
|
+
|
69
|
+
def raw_subject_info
|
70
|
+
@raw_subject_info ||= parse_id_token(access_token["id_token"] || access_token.get("/oauth/userinfo").body) || {}
|
71
|
+
end
|
72
|
+
|
73
|
+
def raw_credential_info
|
74
|
+
@raw_credential_info ||= access_token.to_hash.with_indifferent_access
|
75
|
+
end
|
76
|
+
|
77
|
+
def authorize_params
|
78
|
+
super.tap do |params|
|
79
|
+
options[:authorize_options].each do |v|
|
80
|
+
params[v.to_sym] = request.params[v] if request.params[v]
|
81
|
+
end
|
82
|
+
|
83
|
+
params[:scope] = get_scope(params)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def get_scope(params)
|
90
|
+
raw_scope = params[:scope] || DEFAULT_SCOPE
|
91
|
+
scope_list = raw_scope.split(" ").map { |item| item.split(",") }.flatten
|
92
|
+
scope_list.join(" ")
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_id_token(token)
|
96
|
+
_, header = JWT.decode(token, nil, false)
|
97
|
+
|
98
|
+
keys = request_keys
|
99
|
+
|
100
|
+
public_key_params = keys.find { |key| key["kid"] == header["kid"] }
|
101
|
+
rsa_key = create_rsa_key(public_key_params["n"], public_key_params["e"])
|
102
|
+
|
103
|
+
body, = JWT.decode(token, rsa_key.public_key, true, { algorithm: header["alg"] })
|
104
|
+
body
|
105
|
+
rescue JWT::VerificationError => e
|
106
|
+
raise OmniAuth::DoximityOauth2::JWTVerificationError(e, token)
|
107
|
+
end
|
108
|
+
|
109
|
+
def callback_url
|
110
|
+
options[:callback_url] || full_host + script_name + callback_path
|
111
|
+
end
|
112
|
+
|
113
|
+
def prune(hash)
|
114
|
+
hash.delete_if do |_, val|
|
115
|
+
prune(val) if val.is_a?(Hash)
|
116
|
+
val.nil? || (val.respond_to?(:empty?) && val.empty?)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def request_keys
|
121
|
+
url = options[:client_options][:site] + options[:client_options][:jwks_url]
|
122
|
+
response = Faraday.get(url)
|
123
|
+
|
124
|
+
raise OmniAuth::DoximityOauth2::JWKSRequestError(url, response) if response.status != 200
|
125
|
+
|
126
|
+
MultiJson.load(response.body)["keys"]
|
127
|
+
end
|
128
|
+
|
129
|
+
def create_rsa_key(n, e)
|
130
|
+
key = OpenSSL::PKey::RSA.new
|
131
|
+
key.set_key(OpenSSL::BN.new(Base64.urlsafe_decode64(n), 2), OpenSSL::BN.new(Base64.urlsafe_decode64(e), 2), nil)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Omniauth
|
4
|
+
module DoximityOauth2
|
5
|
+
# Error for failed request to get public keys, for JWK verification
|
6
|
+
class JWKSRequestError < StandardError
|
7
|
+
MESSAGE = "Failed to request public keys for user info verification"
|
8
|
+
attr_reader :url, :response
|
9
|
+
|
10
|
+
def initialize(url, response)
|
11
|
+
@url = url
|
12
|
+
@response = response
|
13
|
+
super(MESSAGE)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Error for failed JWK verifications
|
18
|
+
class JWTVerificationError < StandardError
|
19
|
+
MESSAGE = "Failed to verify user info JWT"
|
20
|
+
attr_reader :token, :error
|
21
|
+
|
22
|
+
def initialize(error, token)
|
23
|
+
@token = token
|
24
|
+
super(error.message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
require File.expand_path('lib/omniauth-doximity-oauth2/version', __dir__)
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "omniauth-doximity-oauth2"
|
8
|
+
spec.version = Omniauth::DoximityOauth2::VERSION
|
9
|
+
spec.authors = ["William Harvey"]
|
10
|
+
spec.email = ["wharvey@doximity.com"]
|
11
|
+
spec.description = 'OmniAuth strategy for Doximity, supporting OIDC, and using PKCE'
|
12
|
+
spec.summary = 'OmniAuth strategy for Doximity'
|
13
|
+
spec.homepage = "https://github.com/doximity/omniauth-doximity-oauth2.git"
|
14
|
+
spec.license = "Apache-2.0"
|
15
|
+
|
16
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/doximity/omniauth-doximity-oauth2/blob/master/CHANGELOG.md"
|
21
|
+
|
22
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
23
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
24
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.add_runtime_dependency "activesupport"
|
28
|
+
spec.add_runtime_dependency "faraday"
|
29
|
+
spec.add_runtime_dependency "jwt"
|
30
|
+
spec.add_runtime_dependency "multi_json"
|
31
|
+
spec.add_runtime_dependency "omniauth-oauth2"
|
32
|
+
spec.add_runtime_dependency "openssl"
|
33
|
+
|
34
|
+
spec.add_development_dependency "bundler", "~> 2.3.12"
|
35
|
+
spec.add_development_dependency "rake"
|
36
|
+
spec.add_development_dependency "rspec"
|
37
|
+
spec.add_development_dependency "rubocop"
|
38
|
+
spec.add_development_dependency "rubocop-rails"
|
39
|
+
spec.add_development_dependency "sdoc"
|
40
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
require "json"
|
5
|
+
require "omniauth-doximity-oauth2"
|
6
|
+
require "stringio"
|
7
|
+
|
8
|
+
describe OmniAuth::Strategies::DoximityOauth2 do
|
9
|
+
let(:request) { double("Request", params: {}, cookies: {}, env: {}) }
|
10
|
+
let(:app) do
|
11
|
+
lambda do
|
12
|
+
[200, {}, ["Hello."]]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
subject do
|
17
|
+
OmniAuth::Strategies::DoximityOauth2.new(app, "appid", "secret", @options || {}).tap do |strategy|
|
18
|
+
allow(strategy).to receive(:request) do
|
19
|
+
request
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
before do
|
25
|
+
OmniAuth.config.test_mode = true
|
26
|
+
end
|
27
|
+
|
28
|
+
after do
|
29
|
+
OmniAuth.config.test_mode = false
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#client_options" do
|
33
|
+
it "has correct site" do
|
34
|
+
expect(subject.client.site).to eq("https://auth.doximity.com")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "has correct authorize_url" do
|
38
|
+
expect(subject.client.options[:authorize_url]).to eq("/oauth/authorize")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "has correct token_url" do
|
42
|
+
expect(subject.client.options[:token_url]).to eq("/oauth/token")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "has correct jwks_url" do
|
46
|
+
expect(subject.client.options[:jwks_url]).to eq("/.well-known/jwks.json")
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "overrides" do
|
50
|
+
context "as strings" do
|
51
|
+
it "should allow overriding the site" do
|
52
|
+
@options = { client_options: { "site" => "https://example.com" } }
|
53
|
+
expect(subject.client.site).to eq("https://example.com")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should allow overriding the authorize_url" do
|
57
|
+
@options = { client_options: { "authorize_url" => "/example" } }
|
58
|
+
expect(subject.client.options[:authorize_url]).to eq("/example")
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should allow overriding the token_url" do
|
62
|
+
@options = { client_options: { "token_url" => "/example" } }
|
63
|
+
expect(subject.client.options[:token_url]).to eq("/example")
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should allow overriding the jwks_url" do
|
67
|
+
@options = { client_options: { "jwks_url" => "/example" } }
|
68
|
+
expect(subject.client.options[:jwks_url]).to eq("/example")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "as symbols" do
|
73
|
+
it "should allow overriding the site" do
|
74
|
+
@options = { client_options: { site: "https://example.com" } }
|
75
|
+
expect(subject.client.site).to eq("https://example.com")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should allow overriding the authorize_url" do
|
79
|
+
@options = { client_options: { authorize_url: "/example" } }
|
80
|
+
expect(subject.client.options[:authorize_url]).to eq("/example")
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should allow overriding the token_url" do
|
84
|
+
@options = { client_options: { token_url: "/example" } }
|
85
|
+
expect(subject.client.options[:token_url]).to eq("/example")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should allow overriding the jwks_url" do
|
89
|
+
@options = { client_options: { jwks_url: "/example" } }
|
90
|
+
expect(subject.client.options[:jwks_url]).to eq("/example")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#authorize_options" do
|
97
|
+
%i[scope].each do |k|
|
98
|
+
it "should support #{k}" do
|
99
|
+
@options = { k => "http://someval" }
|
100
|
+
expect(subject.authorize_params[k.to_s]).to eq("http://someval")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "scope" do
|
105
|
+
it "should leave base scopes as is" do
|
106
|
+
@options = { scope: "profile:read:basic" }
|
107
|
+
expect(subject.authorize_params["scope"]).to eq("profile:read:basic")
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should join scopes" do
|
111
|
+
@options = { scope: "profile:read:basic,profile:read:email" }
|
112
|
+
expect(subject.authorize_params["scope"]).to eq("profile:read:basic profile:read:email")
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should deal with whitespace when joining scopes" do
|
116
|
+
@options = { scope: "profile:read:basic, profile:read:email" }
|
117
|
+
expect(subject.authorize_params["scope"]).to eq("profile:read:basic profile:read:email")
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should set default scope to openid profile:read:basic" do
|
121
|
+
expect(subject.authorize_params["scope"]).to eq("openid profile:read:basic")
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should support space delimited scopes" do
|
125
|
+
@options = { scope: "profile:read:basic profile:read:email" }
|
126
|
+
expect(subject.authorize_params["scope"]).to eq("profile:read:basic profile:read:email")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/tasks/ci.rake
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :ci do
|
4
|
+
desc "Run tests"
|
5
|
+
task :specs do
|
6
|
+
sh "bundle exec rspec --color spec --format progress"
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Run rubocop"
|
10
|
+
task :rubocop do
|
11
|
+
sh "bundle exec rubocop --display-cop-names --extra-details --display-style-guide"
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Build documentation"
|
15
|
+
task doc: :rdoc
|
16
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|