rodauth-oauth 0.0.4 → 0.3.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/CHANGELOG.md +197 -4
- data/README.md +45 -21
- data/lib/generators/roda/oauth/templates/db/migrate/create_rodauth_oauth.rb +8 -5
- data/lib/rodauth/features/oauth.rb +624 -449
- data/lib/rodauth/features/oauth_jwt.rb +244 -113
- data/lib/rodauth/features/oauth_saml.rb +104 -0
- data/lib/rodauth/features/oidc.rb +388 -0
- data/lib/rodauth/oauth/database_extensions.rb +73 -0
- data/lib/rodauth/oauth/ttl_store.rb +59 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- data/templates/authorize.str +33 -0
- data/templates/client_secret_field.str +4 -0
- data/templates/description_field.str +4 -0
- data/templates/homepage_url_field.str +4 -0
- data/templates/name_field.str +4 -0
- data/templates/new_oauth_application.str +10 -0
- data/templates/oauth_application.str +11 -0
- data/templates/oauth_applications.str +14 -0
- data/templates/oauth_tokens.str +49 -0
- data/templates/redirect_uri_field.str +4 -0
- data/templates/scope_field.str +10 -0
- metadata +24 -9
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rodauth
|
4
|
+
module OAuth
|
5
|
+
# rubocop:disable Naming/MethodName, Metrics/ParameterLists
|
6
|
+
def self.ExtendDatabase(db)
|
7
|
+
Module.new do
|
8
|
+
dataset = db.dataset
|
9
|
+
|
10
|
+
if dataset.supports_returning?(:insert)
|
11
|
+
def __insert_and_return__(dataset, _pkey, params)
|
12
|
+
dataset.returning.insert(params).first
|
13
|
+
end
|
14
|
+
else
|
15
|
+
def __insert_and_return__(dataset, pkey, params)
|
16
|
+
id = dataset.insert(params)
|
17
|
+
dataset.where(pkey => id).first
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if dataset.supports_returning?(:update)
|
22
|
+
def __update_and_return__(dataset, params)
|
23
|
+
dataset.returning.update(params).first
|
24
|
+
end
|
25
|
+
else
|
26
|
+
def __update_and_return__(dataset, params)
|
27
|
+
dataset.update(params)
|
28
|
+
dataset.first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if dataset.respond_to?(:supports_insert_conflict?) && dataset.supports_insert_conflict?
|
33
|
+
def __insert_or_update_and_return__(dataset, pkey, unique_columns, params, conds = nil, exclude_on_update = nil)
|
34
|
+
to_update = params.keys - unique_columns
|
35
|
+
to_update -= exclude_on_update if exclude_on_update
|
36
|
+
|
37
|
+
dataset = dataset.insert_conflict(
|
38
|
+
target: unique_columns,
|
39
|
+
update: Hash[ to_update.map { |attribute| [attribute, Sequel[:excluded][attribute]] } ],
|
40
|
+
update_where: conds
|
41
|
+
)
|
42
|
+
|
43
|
+
__insert_and_return__(dataset, pkey, params)
|
44
|
+
end
|
45
|
+
else
|
46
|
+
def __insert_or_update_and_return__(dataset, pkey, unique_columns, params, conds = nil, exclude_on_update = nil)
|
47
|
+
find_params, update_params = params.partition { |key, _| unique_columns.include?(key) }.map { |h| Hash[h] }
|
48
|
+
|
49
|
+
dataset_where = dataset.where(find_params)
|
50
|
+
record = if conds
|
51
|
+
dataset_where_conds = dataset_where.where(conds)
|
52
|
+
|
53
|
+
# this means that there's still a valid entry there, so return early
|
54
|
+
return if dataset_where.count != dataset_where_conds.count
|
55
|
+
|
56
|
+
dataset_where_conds.first
|
57
|
+
else
|
58
|
+
dataset_where.first
|
59
|
+
end
|
60
|
+
|
61
|
+
if record
|
62
|
+
update_params.reject! { |k, _v| exclude_on_update.include?(k) } if exclude_on_update
|
63
|
+
__update_and_return__(dataset_where, update_params)
|
64
|
+
else
|
65
|
+
__insert_and_return__(dataset, pkey, params)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
# rubocop:enable Naming/MethodName, Metrics/ParameterLists
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# The TTL store is a data structure which keeps data by a key, and with a time-to-live.
|
5
|
+
# It is specifically designed for data which is static, i.e. for a certain key in a
|
6
|
+
# sufficiently large span, the value will be the same.
|
7
|
+
#
|
8
|
+
# Because of that, synchronizations around reads do not exist, while write synchronizations
|
9
|
+
# will be short-circuited by a read.
|
10
|
+
#
|
11
|
+
class Rodauth::OAuth::TtlStore
|
12
|
+
DEFAULT_TTL = 60 * 60 * 24 # default TTL is one day
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@store_mutex = Mutex.new
|
16
|
+
@store = Hash.new {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
lookup(key, now)
|
21
|
+
end
|
22
|
+
|
23
|
+
def set(key, &block)
|
24
|
+
@store_mutex.synchronize do
|
25
|
+
# short circuit
|
26
|
+
return @store[key][:payload] if @store[key] && @store[key][:ttl] < now
|
27
|
+
|
28
|
+
payload, ttl = block.call
|
29
|
+
@store[key] = { payload: payload, ttl: (ttl || (now + DEFAULT_TTL)) }
|
30
|
+
|
31
|
+
@store[key][:payload]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def uncache(key)
|
36
|
+
@store_mutex.synchronize do
|
37
|
+
@store.delete(key)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def now
|
44
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
45
|
+
end
|
46
|
+
|
47
|
+
# do not use directly!
|
48
|
+
def lookup(key, ttl)
|
49
|
+
return unless @store.key?(key)
|
50
|
+
|
51
|
+
value = @store[key]
|
52
|
+
|
53
|
+
return if value.empty?
|
54
|
+
|
55
|
+
return unless value[:ttl] > ttl
|
56
|
+
|
57
|
+
value[:payload]
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<form method="post" class="form-horizontal" role="form" id="authorize-form">
|
2
|
+
#{csrf_tag(rodauth.authorize_path) if respond_to?(:csrf_tag)}
|
3
|
+
<p class="lead">The application #{rodauth.oauth_application[rodauth.oauth_applications_name_column]} would like to access your data.</p>
|
4
|
+
|
5
|
+
<div class="form-group">
|
6
|
+
<h1 class="display-6">#{rodauth.scopes_label}</h1>
|
7
|
+
|
8
|
+
#{
|
9
|
+
rodauth.scopes.map do |scope|
|
10
|
+
<<-HTML
|
11
|
+
<div class="form-check">
|
12
|
+
<input id="#{scope}" class="form-check-input" type="checkbox" name="scope[]" value="#{scope}" #{"checked disabled" if scope == rodauth.oauth_application_default_scope}>
|
13
|
+
<label class="form-check-label" for="#{scope}">#{scope}</label>
|
14
|
+
</div>
|
15
|
+
HTML
|
16
|
+
end.join
|
17
|
+
}
|
18
|
+
|
19
|
+
<input type="hidden" name="client_id" value="#{rodauth.param("client_id")}"/>
|
20
|
+
|
21
|
+
#{"<input type=\"hidden\" name=\"access_type\" value=\"#{rodauth.param("access_type")}\"/>" if rodauth.param_or_nil("access_type")}
|
22
|
+
#{"<input type=\"hidden\" name=\"response_type\" value=\"#{rodauth.param("response_type")}\"/>" if rodauth.param_or_nil("response_type")}
|
23
|
+
#{"<input type=\"hidden\" name=\"state\" value=\"#{rodauth.param("state")}\"/>" if rodauth.param_or_nil("state")}
|
24
|
+
#{"<input type=\"hidden\" name=\"nonce\" value=\"#{rodauth.param("nonce")}\"/>" if rodauth.param_or_nil("nonce")}
|
25
|
+
#{"<input type=\"hidden\" name=\"redirect_uri\" value=\"#{rodauth.redirect_uri}\"/>" if rodauth.param_or_nil("redirect_uri")}
|
26
|
+
#{"<input type=\"hidden\" name=\"code_challenge\" value=\"#{rodauth.param("code_challenge")}\"/>" if rodauth.param_or_nil("code_challenge")}
|
27
|
+
#{"<input type=\"hidden\" name=\"code_challenge_method\" value=\"#{rodauth.param("code_challenge_method")}\"/>" if rodauth.param_or_nil("code_challenge_method")}
|
28
|
+
</div>
|
29
|
+
<p class="text-center">
|
30
|
+
<input type="submit" class="btn btn-outline-primary" value="#{h(rodauth.oauth_authorize_button)}"/>
|
31
|
+
<a href="#{rodauth.redirect_uri}?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request#{ "&state=#{rodauth.param("state")}" if rodauth.param_or_nil("state")}" class="btn btn-outline-danger">Cancel</a>
|
32
|
+
</p>
|
33
|
+
</form>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<form method="post" action="#{rodauth.oauth_applications_path}" class="rodauth" role="form" id="oauth-application-form">
|
2
|
+
#{rodauth.csrf_tag}
|
3
|
+
#{rodauth.render('name_field')}
|
4
|
+
#{rodauth.render('description_field')}
|
5
|
+
#{rodauth.render('homepage_url_field')}
|
6
|
+
#{rodauth.render('redirect_uri_field')}
|
7
|
+
#{rodauth.render('client_secret_field')}
|
8
|
+
#{rodauth.render('scope_field')}
|
9
|
+
#{rodauth.button(rodauth.oauth_application_button)}
|
10
|
+
</form>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<div id="oauth-application">
|
2
|
+
<dl>
|
3
|
+
#{
|
4
|
+
(rodauth.oauth_application_required_params + %w[client_id] - %w[client_secret]).map do |param|
|
5
|
+
"<dt class=\"#{param}\">#{rodauth.send(:"#{param}_label")}</dt>" +
|
6
|
+
"<dd class=\"#{param}\">#{@oauth_application[rodauth.send(:"oauth_applications_#{param}_column")]}</dd>"
|
7
|
+
end.join
|
8
|
+
}
|
9
|
+
</dl>
|
10
|
+
<a href="/#{"#{rodauth.oauth_applications_path}/#{@oauth_application[:id]}/#{rodauth.oauth_tokens_path}"}" class="btn btn-outline-secondary">Oauth Tokens</a>
|
11
|
+
</div>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<div id="oauth-applications">
|
2
|
+
<a class="btn btn-outline-primary" href="/oauth-applications/new">Register new Oauth Application</a>
|
3
|
+
#{
|
4
|
+
if @oauth_applications.count.zero?
|
5
|
+
"<p>No oauth applications yet!</p>"
|
6
|
+
else
|
7
|
+
"<ul class=\"list-group\">" +
|
8
|
+
@oauth_applications.map do |application|
|
9
|
+
"<li class=\"list-group-item\"><a href=\"/oauth-applications/#{application[:id]}\">#{application[:name]}</a></li>"
|
10
|
+
end.join +
|
11
|
+
"</ul>"
|
12
|
+
end
|
13
|
+
}
|
14
|
+
</div>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
<div id="oauth-tokens">
|
2
|
+
#{
|
3
|
+
if @oauth_tokens.count.zero?
|
4
|
+
"<p>No oauth tokens yet!</p>"
|
5
|
+
else
|
6
|
+
<<-HTML
|
7
|
+
<table class="table">
|
8
|
+
<thead>
|
9
|
+
<tr>
|
10
|
+
<th scope="col">Token</th>
|
11
|
+
<th scope="col">Refresh Token</th>
|
12
|
+
<th scope="col">Expires in</th>
|
13
|
+
<th scope="col">Revoke</th>
|
14
|
+
<th scope="col"><span class="badge badge-pill badge-dark">#{@oauth_tokens.count}</span>
|
15
|
+
</tr>
|
16
|
+
</thead>
|
17
|
+
<tbody>
|
18
|
+
#{
|
19
|
+
@oauth_tokens.map do |oauth_token|
|
20
|
+
<<-HTML
|
21
|
+
<tr>
|
22
|
+
<td>#{oauth_token[rodauth.oauth_tokens_token_column]}</td>
|
23
|
+
<td>#{oauth_token[rodauth.oauth_tokens_refresh_token_column]}</td>
|
24
|
+
<td>#{rodauth.convert_timestamp(oauth_token[rodauth.oauth_tokens_expires_in_column])}</td>
|
25
|
+
<td>#{rodauth.convert_timestamp(oauth_token[rodauth.oauth_tokens_revoked_at_column])}</td>
|
26
|
+
<td>
|
27
|
+
#{
|
28
|
+
if !oauth_token[rodauth.oauth_tokens_revoked_at_param] && !oauth_token[rodauth.oauth_tokens_token_hash_column]
|
29
|
+
<<-HTML
|
30
|
+
<form method="post" action="#{rodauth.revoke_path}" class="form-horizontal" role="form" id="revoke-form">
|
31
|
+
#{csrf_tag(rodauth.oauth_revoke_path) if respond_to?(:csrf_tag)}
|
32
|
+
#{rodauth.input_field_string("token_type_hint", "revoke-token-type-hint", :value => "access_token", :type=>"hidden")}
|
33
|
+
#{rodauth.input_field_string("token", "revoke-token", :value => oauth_token[rodauth.oauth_tokens_token_column], :type=>"hidden")}
|
34
|
+
#{rodauth.button(rodauth.oauth_token_revoke_button)}
|
35
|
+
</form>
|
36
|
+
HTML
|
37
|
+
end
|
38
|
+
}
|
39
|
+
</td>
|
40
|
+
</tr>
|
41
|
+
HTML
|
42
|
+
end.join
|
43
|
+
}
|
44
|
+
</tbody>
|
45
|
+
</table>
|
46
|
+
HTML
|
47
|
+
end
|
48
|
+
}
|
49
|
+
</div>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<fieldset class="form-group">
|
2
|
+
#{
|
3
|
+
rodauth.oauth_application_scopes.map do |scope|
|
4
|
+
"<div class=\"form-check checkbox\">" +
|
5
|
+
"<input id=\"#{scope}\" type=\"checkbox\" name=\"#{rodauth.oauth_application_scopes_param}[]\" value=\"#{scope}\">" +
|
6
|
+
"<label for=\"#{scope}\">#{scope}</label>" +
|
7
|
+
"</div>"
|
8
|
+
end.join
|
9
|
+
}
|
10
|
+
</fieldset>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth-oauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Implementation of the OAuth 2.0 protocol on top of rodauth.
|
14
14
|
email:
|
@@ -32,16 +32,31 @@ files:
|
|
32
32
|
- lib/rodauth/features/oauth.rb
|
33
33
|
- lib/rodauth/features/oauth_http_mac.rb
|
34
34
|
- lib/rodauth/features/oauth_jwt.rb
|
35
|
+
- lib/rodauth/features/oauth_saml.rb
|
36
|
+
- lib/rodauth/features/oidc.rb
|
35
37
|
- lib/rodauth/oauth.rb
|
38
|
+
- lib/rodauth/oauth/database_extensions.rb
|
36
39
|
- lib/rodauth/oauth/railtie.rb
|
40
|
+
- lib/rodauth/oauth/ttl_store.rb
|
37
41
|
- lib/rodauth/oauth/version.rb
|
38
|
-
|
42
|
+
- templates/authorize.str
|
43
|
+
- templates/client_secret_field.str
|
44
|
+
- templates/description_field.str
|
45
|
+
- templates/homepage_url_field.str
|
46
|
+
- templates/name_field.str
|
47
|
+
- templates/new_oauth_application.str
|
48
|
+
- templates/oauth_application.str
|
49
|
+
- templates/oauth_applications.str
|
50
|
+
- templates/oauth_tokens.str
|
51
|
+
- templates/redirect_uri_field.str
|
52
|
+
- templates/scope_field.str
|
53
|
+
homepage: https://gitlab.com/honeyryderchuck/rodauth-oauth
|
39
54
|
licenses: []
|
40
55
|
metadata:
|
41
|
-
homepage_uri: https://gitlab.com/honeyryderchuck/
|
42
|
-
source_code_uri: https://gitlab.com/honeyryderchuck/
|
43
|
-
changelog_uri: https://gitlab.com/honeyryderchuck/
|
44
|
-
post_install_message:
|
56
|
+
homepage_uri: https://gitlab.com/honeyryderchuck/rodauth-oauth
|
57
|
+
source_code_uri: https://gitlab.com/honeyryderchuck/rodauth-oauth
|
58
|
+
changelog_uri: https://gitlab.com/honeyryderchuck/rodauth-oauth/-/blob/master/CHANGELOG.md
|
59
|
+
post_install_message:
|
45
60
|
rdoc_options: []
|
46
61
|
require_paths:
|
47
62
|
- lib
|
@@ -57,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
72
|
version: '0'
|
58
73
|
requirements: []
|
59
74
|
rubygems_version: 3.1.2
|
60
|
-
signing_key:
|
75
|
+
signing_key:
|
61
76
|
specification_version: 4
|
62
77
|
summary: Implementation of the OAuth 2.0 protocol on top of rodauth.
|
63
78
|
test_files: []
|