File: refresh_token_request.rb

package info (click to toggle)
ruby-doorkeeper 5.9.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,000 kB
  • sloc: ruby: 4,699; makefile: 4
file content (141 lines) | stat: -rw-r--r-- 4,589 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# frozen_string_literal: true

module Doorkeeper
  module OAuth
    class RefreshTokenRequest < BaseRequest
      include OAuth::Helpers

      validate :token_presence, error: Errors::InvalidRequest
      validate :token,        error: Errors::InvalidGrant
      validate :client,       error: Errors::InvalidClient
      validate :client_match, error: Errors::InvalidGrant
      validate :scope,        error: Errors::InvalidScope

      attr_reader :access_token, :client, :credentials, :refresh_token
      attr_reader :missing_param

      def initialize(server, refresh_token, credentials, parameters = {})
        @server = server
        @refresh_token = refresh_token
        @credentials = credentials
        @original_scopes = parameters[:scope] || parameters[:scopes]
        @refresh_token_parameter = parameters[:refresh_token]
        @client = load_client(credentials) if credentials
      end

      private

      def load_client(credentials)
        Doorkeeper.config.application_model.by_uid_and_secret(credentials.uid, credentials.secret)
      end

      def before_successful_response
        if refresh_token_revoked_on_use?
          # No locking needed when refresh tokens are revoked on use
          # because the old token is revoked later when the new token is used.
          # This allows multiple concurrent refresh requests to succeed during the
          # transition period, after which the old refresh token will be revoked.
          raise Errors::InvalidGrantReuse if refresh_token.revoked?
          create_access_token
        else
          # Use locking when refresh tokens are revoked immediately
          # to prevent race conditions where multiple tokens could be created
          refresh_token.with_lock do
            raise Errors::InvalidGrantReuse if refresh_token.revoked?
            refresh_token.revoke
            create_access_token
          end
        end
        super
      end

      def refresh_token_revoked_on_use?
        Doorkeeper.config.access_token_model.refresh_token_revoked_on_use?
      end

      def default_scopes
        refresh_token.scopes
      end

      def create_access_token
        attributes = {}.merge(custom_token_attributes_with_data)

        resource_owner =
          if Doorkeeper.config.polymorphic_resource_owner?
            refresh_token.resource_owner
          else
            refresh_token.resource_owner_id
          end

        if refresh_token_revoked_on_use?
          attributes[:previous_refresh_token] = refresh_token.refresh_token
        end

        # RFC6749
        # 1.5.  Refresh Token
        #
        # Refresh tokens are issued to the client by the authorization server and are
        # used to obtain a new access token when the current access token
        # becomes invalid or expires, or to obtain additional access tokens
        # with identical or narrower scope (access tokens may have a shorter
        # lifetime and fewer permissions than authorized by the resource
        # owner).
        #
        # Here we assume that TTL of the token received after refreshing should be
        # the same as that of the original token.
        #
        @access_token = Doorkeeper.config.access_token_model.create_for(
          application: refresh_token.application,
          resource_owner: resource_owner,
          scopes: scopes,
          expires_in: refresh_token.expires_in,
          use_refresh_token: true,
          **attributes,
        )
      end

      def validate_token_presence
        @missing_param = :refresh_token if refresh_token.blank? && @refresh_token_parameter.blank?

        @missing_param.nil?
      end

      def validate_token
        refresh_token.present? && !refresh_token.revoked?
      end

      def validate_client
        return true if credentials.blank?

        client.present?
      end

      # @see https://datatracker.ietf.org/doc/html/rfc6749#section-1.5
      #
      def validate_client_match
        return true if refresh_token.application_id.blank?

        client && refresh_token.application_id == client.id
      end

      def validate_scope
        if @original_scopes.present?
          ScopeChecker.valid?(
            scope_str: @original_scopes,
            server_scopes: refresh_token.scopes,
          )
        else
          true
        end
      end

      def custom_token_attributes_with_data
        refresh_token
        .attributes
        .with_indifferent_access
        .slice(*Doorkeeper.config.custom_access_token_attributes)
        .symbolize_keys
      end
    end
  end
end