doorkeeper 5.0.0 → 5.0.1

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.

Potentially problematic release.


This version of doorkeeper might be problematic. Click here for more details.

Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -0
  3. data/Dangerfile +57 -0
  4. data/NEWS.md +32 -1
  5. data/README.md +18 -3
  6. data/app/controllers/doorkeeper/application_controller.rb +2 -0
  7. data/app/controllers/doorkeeper/application_metal_controller.rb +2 -0
  8. data/app/controllers/doorkeeper/applications_controller.rb +4 -2
  9. data/app/controllers/doorkeeper/authorizations_controller.rb +3 -3
  10. data/app/controllers/doorkeeper/authorized_applications_controller.rb +2 -0
  11. data/app/controllers/doorkeeper/token_info_controller.rb +2 -0
  12. data/app/controllers/doorkeeper/tokens_controller.rb +2 -0
  13. data/app/helpers/doorkeeper/dashboard_helper.rb +2 -0
  14. data/app/validators/redirect_uri_validator.rb +2 -0
  15. data/doorkeeper.gemspec +23 -22
  16. data/lib/doorkeeper.rb +1 -0
  17. data/lib/doorkeeper/config.rb +7 -2
  18. data/lib/doorkeeper/engine.rb +2 -0
  19. data/lib/doorkeeper/errors.rb +17 -0
  20. data/lib/doorkeeper/grape/authorization_decorator.rb +2 -0
  21. data/lib/doorkeeper/grape/helpers.rb +2 -0
  22. data/lib/doorkeeper/helpers/controller.rb +2 -0
  23. data/lib/doorkeeper/models/access_grant_mixin.rb +5 -3
  24. data/lib/doorkeeper/models/access_token_mixin.rb +5 -3
  25. data/lib/doorkeeper/models/application_mixin.rb +2 -0
  26. data/lib/doorkeeper/models/concerns/accessible.rb +2 -0
  27. data/lib/doorkeeper/models/concerns/expirable.rb +2 -0
  28. data/lib/doorkeeper/models/concerns/orderable.rb +2 -0
  29. data/lib/doorkeeper/models/concerns/ownership.rb +2 -0
  30. data/lib/doorkeeper/models/concerns/revocable.rb +2 -0
  31. data/lib/doorkeeper/models/concerns/scopes.rb +2 -0
  32. data/lib/doorkeeper/oauth/authorization/code.rb +2 -0
  33. data/lib/doorkeeper/oauth/authorization/context.rb +2 -0
  34. data/lib/doorkeeper/oauth/authorization/token.rb +2 -0
  35. data/lib/doorkeeper/oauth/authorization/uri_builder.rb +2 -0
  36. data/lib/doorkeeper/oauth/authorization_code_request.rb +2 -0
  37. data/lib/doorkeeper/oauth/base_request.rb +2 -0
  38. data/lib/doorkeeper/oauth/base_response.rb +2 -0
  39. data/lib/doorkeeper/oauth/client.rb +2 -0
  40. data/lib/doorkeeper/oauth/client/credentials.rb +2 -0
  41. data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -0
  42. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +2 -0
  43. data/lib/doorkeeper/oauth/client_credentials/validation.rb +2 -0
  44. data/lib/doorkeeper/oauth/client_credentials_request.rb +2 -0
  45. data/lib/doorkeeper/oauth/code_request.rb +2 -0
  46. data/lib/doorkeeper/oauth/code_response.rb +2 -0
  47. data/lib/doorkeeper/oauth/error.rb +2 -0
  48. data/lib/doorkeeper/oauth/error_response.rb +10 -0
  49. data/lib/doorkeeper/oauth/forbidden_token_response.rb +9 -2
  50. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +2 -0
  51. data/lib/doorkeeper/oauth/helpers/unique_token.rb +2 -0
  52. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +5 -2
  53. data/lib/doorkeeper/oauth/invalid_token_response.rb +18 -0
  54. data/lib/doorkeeper/oauth/password_access_token_request.rb +2 -0
  55. data/lib/doorkeeper/oauth/pre_authorization.rb +2 -0
  56. data/lib/doorkeeper/oauth/refresh_token_request.rb +10 -2
  57. data/lib/doorkeeper/oauth/scopes.rb +2 -0
  58. data/lib/doorkeeper/oauth/token.rb +2 -0
  59. data/lib/doorkeeper/oauth/token_introspection.rb +2 -0
  60. data/lib/doorkeeper/oauth/token_request.rb +2 -0
  61. data/lib/doorkeeper/oauth/token_response.rb +2 -0
  62. data/lib/doorkeeper/orm/active_record.rb +2 -0
  63. data/lib/doorkeeper/rails/helpers.rb +4 -0
  64. data/lib/doorkeeper/rails/routes.rb +9 -2
  65. data/lib/doorkeeper/rails/routes/mapper.rb +2 -0
  66. data/lib/doorkeeper/rails/routes/mapping.rb +2 -0
  67. data/lib/doorkeeper/rake/db.rake +4 -4
  68. data/lib/doorkeeper/request.rb +2 -0
  69. data/lib/doorkeeper/request/authorization_code.rb +2 -0
  70. data/lib/doorkeeper/request/client_credentials.rb +2 -0
  71. data/lib/doorkeeper/request/code.rb +2 -0
  72. data/lib/doorkeeper/request/password.rb +2 -0
  73. data/lib/doorkeeper/request/refresh_token.rb +2 -0
  74. data/lib/doorkeeper/request/strategy.rb +2 -0
  75. data/lib/doorkeeper/request/token.rb +2 -0
  76. data/lib/doorkeeper/server.rb +2 -0
  77. data/lib/doorkeeper/stale_records_cleaner.rb +20 -0
  78. data/lib/doorkeeper/validations.rb +2 -0
  79. data/lib/doorkeeper/version.rb +3 -1
  80. data/lib/generators/doorkeeper/templates/initializer.rb +20 -2
  81. data/lib/generators/doorkeeper/templates/migration.rb.erb +2 -2
  82. data/spec/controllers/applications_controller_spec.rb +37 -41
  83. data/spec/controllers/authorizations_controller_spec.rb +71 -18
  84. data/spec/controllers/protected_resources_controller_spec.rb +44 -2
  85. data/spec/controllers/tokens_controller_spec.rb +4 -5
  86. data/spec/dummy/Rakefile +1 -1
  87. data/spec/dummy/app/controllers/custom_authorizations_controller.rb +1 -1
  88. data/spec/dummy/app/controllers/home_controller.rb +1 -2
  89. data/spec/dummy/config.ru +1 -1
  90. data/spec/dummy/config/application.rb +1 -1
  91. data/spec/dummy/config/boot.rb +2 -4
  92. data/spec/dummy/config/environment.rb +1 -1
  93. data/spec/dummy/config/environments/test.rb +1 -1
  94. data/spec/dummy/config/initializers/doorkeeper.rb +2 -1
  95. data/spec/dummy/config/initializers/new_framework_defaults.rb +1 -3
  96. data/spec/dummy/config/initializers/secret_token.rb +1 -1
  97. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +4 -4
  98. data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +1 -1
  99. data/spec/dummy/script/rails +4 -3
  100. data/spec/factories.rb +6 -6
  101. data/spec/generators/install_generator_spec.rb +4 -1
  102. data/spec/generators/templates/routes.rb +0 -1
  103. data/spec/generators/views_generator_spec.rb +1 -1
  104. data/spec/grape/grape_integration_spec.rb +1 -1
  105. data/spec/lib/config_spec.rb +25 -8
  106. data/spec/lib/doorkeeper_spec.rb +5 -5
  107. data/spec/lib/oauth/authorization_code_request_spec.rb +9 -6
  108. data/spec/lib/oauth/base_request_spec.rb +10 -10
  109. data/spec/lib/oauth/client/credentials_spec.rb +2 -2
  110. data/spec/lib/oauth/client_credentials/issuer_spec.rb +0 -2
  111. data/spec/lib/oauth/client_credentials/validation_spec.rb +2 -1
  112. data/spec/lib/oauth/client_credentials_integration_spec.rb +1 -1
  113. data/spec/lib/oauth/code_request_spec.rb +2 -2
  114. data/spec/lib/oauth/code_response_spec.rb +1 -1
  115. data/spec/lib/oauth/helpers/scope_checker_spec.rb +8 -8
  116. data/spec/lib/oauth/helpers/uri_checker_spec.rb +17 -6
  117. data/spec/lib/oauth/password_access_token_request_spec.rb +17 -5
  118. data/spec/lib/oauth/refresh_token_request_spec.rb +11 -7
  119. data/spec/lib/oauth/token_request_spec.rb +5 -5
  120. data/spec/lib/oauth/token_spec.rb +4 -1
  121. data/spec/lib/server_spec.rb +6 -6
  122. data/spec/lib/{orm/active_record/stale_records_cleaner_spec.rb → stale_records_cleaner_spec.rb} +14 -4
  123. data/spec/models/doorkeeper/access_token_spec.rb +14 -10
  124. data/spec/models/doorkeeper/application_spec.rb +4 -4
  125. data/spec/requests/applications/applications_request_spec.rb +2 -2
  126. data/spec/requests/endpoints/authorization_spec.rb +2 -2
  127. data/spec/requests/flows/authorization_code_errors_spec.rb +1 -1
  128. data/spec/requests/flows/authorization_code_spec.rb +75 -15
  129. data/spec/requests/flows/implicit_grant_errors_spec.rb +2 -2
  130. data/spec/requests/flows/password_spec.rb +6 -2
  131. data/spec/requests/flows/refresh_token_spec.rb +57 -0
  132. data/spec/requests/flows/revoke_token_spec.rb +9 -9
  133. data/spec/requests/protected_resources/private_api_spec.rb +2 -2
  134. data/spec/support/doorkeeper_rspec.rb +2 -1
  135. data/spec/support/helpers/model_helper.rb +8 -4
  136. data/spec/support/helpers/url_helper.rb +11 -11
  137. data/spec/support/shared/controllers_shared_context.rb +56 -0
  138. data/spec/validators/redirect_uri_validator_spec.rb +2 -2
  139. metadata +20 -4
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module AccessGrantMixin
3
5
  extend ActiveSupport::Concern
@@ -42,8 +44,8 @@ module Doorkeeper
42
44
  def revoke_all_for(application_id, resource_owner, clock = Time)
43
45
  where(application_id: application_id,
44
46
  resource_owner_id: resource_owner.id,
45
- revoked_at: nil).
46
- update_all(revoked_at: clock.now.utc)
47
+ revoked_at: nil)
48
+ .update_all(revoked_at: clock.now.utc)
47
49
  end
48
50
 
49
51
  # Implements PKCE code_challenge encoding without base64 padding as described in the spec.
@@ -78,7 +80,7 @@ module Doorkeeper
78
80
  #
79
81
  # urlsafe_encode64(bin)
80
82
  # Returns the Base64-encoded version of bin. This method complies with
81
- # Base 64 Encoding with URL and Filename Safe Alphabet in RFC 4648.
83
+ # "Base 64 Encoding with URL and Filename Safe Alphabet" in RFC 4648.
82
84
  # The alphabet uses '-' instead of '+' and '_' instead of '/'.
83
85
 
84
86
  # @param code_verifier [#to_s] a one time use value (any object that responds to `#to_s`)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module AccessTokenMixin
3
5
  extend ActiveSupport::Concern
@@ -47,11 +49,11 @@ module Doorkeeper
47
49
  def revoke_all_for(application_id, resource_owner, clock = Time)
48
50
  where(application_id: application_id,
49
51
  resource_owner_id: resource_owner.id,
50
- revoked_at: nil).
51
- update_all(revoked_at: clock.now.utc)
52
+ revoked_at: nil)
53
+ .update_all(revoked_at: clock.now.utc)
52
54
  end
53
55
 
54
- # Looking for not expired Access Token with a matching set of scopes
56
+ # Looking for not revoked Access Token with a matching set of scopes
55
57
  # that belongs to specific Application and Resource Owner.
56
58
  #
57
59
  # @param application [Doorkeeper::Application]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module ApplicationMixin
3
5
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Accessible
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Expirable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Orderable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Ownership
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Revocable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Scopes
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  module Authorization
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  module Authorization
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  module Authorization
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
 
3
5
  module Doorkeeper
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class AuthorizationCodeRequest < BaseRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class BaseRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class BaseResponse
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class Client
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class Client
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class ClientCredentialsRequest < BaseRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class ClientCredentialsRequest < BaseRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class ClientCredentialsRequest < BaseRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class ClientCredentialsRequest < BaseRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class CodeRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class CodeResponse < BaseResponse
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  Error = Struct.new(:name, :state) do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class ErrorResponse < BaseResponse
@@ -55,6 +57,10 @@ module Doorkeeper
55
57
  }
56
58
  end
57
59
 
60
+ def raise_exception!
61
+ raise exception_class.new(self), description
62
+ end
63
+
58
64
  protected
59
65
 
60
66
  delegate :realm, to: :configuration
@@ -63,6 +69,10 @@ module Doorkeeper
63
69
  Doorkeeper.configuration
64
70
  end
65
71
 
72
+ def exception_class
73
+ raise NotImplementedError, "error response must define #exception_class"
74
+ end
75
+
66
76
  private
67
77
 
68
78
  def authenticate_info
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class ForbiddenTokenResponse < ErrorResponse
@@ -21,8 +23,13 @@ module Doorkeeper
21
23
  end
22
24
 
23
25
  def description
24
- scope = { scope: %i[doorkeeper scopes] }
25
- @description ||= @scopes.map { |r| I18n.translate r, scope }.join('\n')
26
+ @description ||= @scopes.map { |s| I18n.t(s, scope: %i[doorkeeper scopes]) }.join("\n")
27
+ end
28
+
29
+ protected
30
+
31
+ def exception_class
32
+ Doorkeeper::Errors::TokenForbidden
26
33
  end
27
34
  end
28
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  module Helpers
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  module Helpers
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  module Helpers
@@ -14,12 +16,13 @@ module Doorkeeper
14
16
  url = as_uri(url)
15
17
  client_url = as_uri(client_url)
16
18
 
17
- if client_url.query.present?
19
+ unless client_url.query.nil?
18
20
  return false unless query_matches?(url.query, client_url.query)
19
21
  # Clear out queries so rest of URI can be tested. This allows query
20
22
  # params to be in the request but order not mattering.
21
23
  client_url.query = nil
22
24
  end
25
+
23
26
  url.query = nil
24
27
  url == client_url
25
28
  end
@@ -33,7 +36,7 @@ module Doorkeeper
33
36
  end
34
37
 
35
38
  def self.query_matches?(query, client_query)
36
- return true if client_query.nil? && query.nil?
39
+ return true if client_query.blank? && query.blank?
37
40
  return false if client_query.nil? || query.nil?
38
41
  # Will return true independent of query order
39
42
  client_query.split('&').sort == query.split('&').sort
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class InvalidTokenResponse < ErrorResponse
@@ -24,6 +26,22 @@ module Doorkeeper
24
26
  scope = { scope: %i[doorkeeper errors messages invalid_token] }
25
27
  @description ||= I18n.translate @reason, scope
26
28
  end
29
+
30
+ protected
31
+
32
+ def exception_class
33
+ errors_mapping.fetch(reason)
34
+ end
35
+
36
+ private
37
+
38
+ def errors_mapping
39
+ {
40
+ expired: Doorkeeper::Errors::TokenExpired,
41
+ revoked: Doorkeeper::Errors::TokenRevoked,
42
+ unknown: Doorkeeper::Errors::TokenUnknown
43
+ }
44
+ end
27
45
  end
28
46
  end
29
47
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class PasswordAccessTokenRequest < BaseRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class PreAuthorization
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class RefreshTokenRequest < BaseRequest
@@ -82,11 +84,17 @@ module Doorkeeper
82
84
  end
83
85
 
84
86
  def validate_client
85
- !credentials || !!client
87
+ return true if credentials.blank?
88
+
89
+ client.present?
86
90
  end
87
91
 
92
+ # @see https://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-1.5
93
+ #
88
94
  def validate_client_match
89
- !client || refresh_token.application_id == client.id
95
+ return true if refresh_token.application_id.blank?
96
+
97
+ client && refresh_token.application_id == client.id
90
98
  end
91
99
 
92
100
  def validate_scope
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class Scopes
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class Token
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  # RFC7662 OAuth 2.0 Token Introspection
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class TokenRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class TokenResponse
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/lazy_load_hooks'
2
4
 
3
5
  require 'doorkeeper/orm/active_record/stale_records_cleaner'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Rails
3
5
  module Helpers
@@ -19,6 +21,8 @@ module Doorkeeper
19
21
 
20
22
  def doorkeeper_render_error
21
23
  error = doorkeeper_error
24
+ error.raise_exception! if Doorkeeper.configuration.raise_on_errors?
25
+
22
26
  headers.merge!(error.headers.reject { |k| k == "Content-Type" })
23
27
  doorkeeper_render_error_with(error)
24
28
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'doorkeeper/rails/routes/mapping'
2
4
  require 'doorkeeper/rails/routes/mapper'
3
5
 
@@ -91,11 +93,16 @@ module Doorkeeper
91
93
  end
92
94
 
93
95
  def application_routes(mapping)
94
- routes.resources :doorkeeper_applications, controller: mapping[:controllers], as: :applications, path: 'applications'
96
+ routes.resources :doorkeeper_applications,
97
+ controller: mapping[:controllers],
98
+ as: :applications,
99
+ path: 'applications'
95
100
  end
96
101
 
97
102
  def authorized_applications_routes(mapping)
98
- routes.resources :authorized_applications, only: %i[index destroy], controller: mapping[:controllers]
103
+ routes.resources :authorized_applications,
104
+ only: %i[index destroy],
105
+ controller: mapping[:controllers]
99
106
  end
100
107
  end
101
108
  end