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.
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- VERSION = "0.0.4"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  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,4 @@
1
+ <div class="form-group">
2
+ <label for="client_secret">#{rodauth.client_secret_label}#{rodauth.input_field_label_suffix}</label>
3
+ #{rodauth.input_field_string(rodauth.oauth_application_client_secret_param, "client_secret", :type=>"text")}
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="form-group">
2
+ <label for="description">#{rodauth.description_label}#{rodauth.input_field_label_suffix}</label>
3
+ #{rodauth.input_field_string(rodauth.oauth_application_description_param, "description", :type=>"text", :required => false)}
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="form-group">
2
+ <label for="homepage_url">#{rodauth.homepage_url_label}#{rodauth.input_field_label_suffix}</label>
3
+ #{rodauth.input_field_string(rodauth.oauth_application_homepage_url_param, "homepage_url", :type=>"text")}
4
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="form-group">
2
+ <label for="name">#{rodauth.name_label}#{rodauth.input_field_label_suffix}</label>
3
+ #{rodauth.input_field_string(rodauth.oauth_application_name_param, "name", :type=>"text")}
4
+ </div>
@@ -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,4 @@
1
+ <div class="form-group">
2
+ <label for="redirect_uri">#{rodauth.redirect_uri_label}#{rodauth.input_field_label_suffix}</label>
3
+ #{rodauth.input_field_string(rodauth.oauth_application_redirect_uri_param, "redirect_uri", :type=>"text")}
4
+ </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
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-06-13 00:00:00.000000000 Z
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
- homepage: https://gitlab.com/honeyryderchuck/roda-oauth
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/roda-oauth
42
- source_code_uri: https://gitlab.com/honeyryderchuck/roda-oauth
43
- changelog_uri: https://gitlab.com/honeyryderchuck/roda-oauth/-/blob/master/CHANGELOG.md
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: []