rodauth-oauth 0.0.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []