doorkeeper 0.4.2 → 0.5.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of doorkeeper might be problematic. Click here for more details.
- data/.gitignore +2 -0
- data/.travis.yml +5 -1
- data/CHANGELOG.md +29 -0
- data/Gemfile +12 -4
- data/README.md +76 -7
- data/Rakefile +1 -25
- data/app/assets/javascripts/doorkeeper/application.js +0 -7
- data/app/controllers/doorkeeper/application_controller.rb +1 -27
- data/app/controllers/doorkeeper/applications_controller.rb +14 -6
- data/app/controllers/doorkeeper/authorized_applications_controller.rb +1 -1
- data/app/controllers/doorkeeper/token_info_controller.rb +11 -0
- data/app/controllers/doorkeeper/tokens_controller.rb +11 -8
- data/app/validators/redirect_uri_validator.rb +12 -0
- data/app/views/doorkeeper/applications/_form.html.erb +3 -3
- data/app/views/doorkeeper/applications/edit.html.erb +1 -1
- data/app/views/doorkeeper/applications/index.html.erb +4 -4
- data/app/views/doorkeeper/applications/new.html.erb +1 -1
- data/app/views/doorkeeper/applications/show.html.erb +3 -3
- data/app/views/doorkeeper/authorizations/new.html.erb +2 -2
- data/app/views/doorkeeper/authorized_applications/index.html.erb +1 -1
- data/config/locales/en.yml +35 -0
- data/doorkeeper.gemspec +3 -3
- data/gemfiles/gemfile.rails-3.1.x +10 -0
- data/gemfiles/gemfile.rails-3.2.x +10 -0
- data/lib/doorkeeper.rb +10 -3
- data/lib/doorkeeper/config.rb +56 -38
- data/lib/doorkeeper/doorkeeper_for.rb +2 -0
- data/lib/doorkeeper/engine.rb +3 -32
- data/lib/doorkeeper/helpers/controller.rb +29 -0
- data/lib/doorkeeper/helpers/filter.rb +4 -18
- data/{app/models/doorkeeper → lib/doorkeeper/models}/access_grant.rb +7 -7
- data/{app/models/doorkeeper → lib/doorkeeper/models}/access_token.rb +27 -24
- data/lib/doorkeeper/models/accessible.rb +9 -0
- data/lib/doorkeeper/models/active_record/access_grant.rb +5 -0
- data/lib/doorkeeper/models/active_record/access_token.rb +15 -0
- data/lib/doorkeeper/models/active_record/application.rb +18 -0
- data/lib/doorkeeper/models/application.rb +38 -0
- data/lib/doorkeeper/models/expirable.rb +6 -4
- data/lib/doorkeeper/models/mongoid/access_grant.rb +22 -0
- data/lib/doorkeeper/models/mongoid/access_token.rb +35 -0
- data/lib/doorkeeper/models/mongoid/application.rb +22 -0
- data/lib/doorkeeper/models/mongoid/revocable.rb +15 -0
- data/lib/doorkeeper/models/mongoid/scopes.rb +15 -0
- data/lib/doorkeeper/models/ownership.rb +16 -0
- data/lib/doorkeeper/models/revocable.rb +1 -1
- data/lib/doorkeeper/models/scopes.rb +9 -5
- data/lib/doorkeeper/oauth/access_token_request.rb +2 -2
- data/lib/doorkeeper/oauth/authorization.rb +1 -0
- data/lib/doorkeeper/oauth/authorization/code.rb +5 -3
- data/lib/doorkeeper/oauth/client.rb +2 -2
- data/lib/doorkeeper/oauth/client_credentials_request.rb +4 -1
- data/lib/doorkeeper/oauth/helpers/unique_token.rb +2 -5
- data/lib/doorkeeper/oauth/password_access_token_request.rb +2 -5
- data/lib/doorkeeper/oauth/token.rb +36 -0
- data/lib/doorkeeper/rails/routes.rb +77 -0
- data/lib/doorkeeper/rails/routes/mapper.rb +28 -0
- data/lib/doorkeeper/rails/routes/mapping.rb +39 -0
- data/lib/doorkeeper/version.rb +1 -1
- data/lib/generators/doorkeeper/application_owner_generator.rb +15 -0
- data/lib/generators/doorkeeper/install_generator.rb +2 -9
- data/lib/generators/doorkeeper/migration_generator.rb +15 -0
- data/lib/generators/doorkeeper/templates/README +15 -1
- data/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb +7 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +31 -15
- data/lib/generators/doorkeeper/templates/migration.rb +7 -4
- data/lib/generators/doorkeeper/views_generator.rb +1 -1
- data/script/run_all +3 -0
- data/spec/controllers/applications_controller_spec.rb +1 -1
- data/spec/controllers/authorizations_controller_spec.rb +4 -4
- data/spec/controllers/protected_resources_controller_spec.rb +7 -7
- data/spec/controllers/token_info_controller_spec.rb +54 -0
- data/spec/controllers/tokens_controller_spec.rb +3 -2
- data/spec/dummy/app/controllers/custom_authorizations_controller.rb +7 -0
- data/spec/dummy/app/models/user.rb +16 -5
- data/spec/dummy/config/application.rb +4 -7
- data/spec/dummy/config/boot.rb +3 -7
- data/spec/dummy/config/initializers/doorkeeper.rb +13 -0
- data/spec/dummy/config/mongoid.yml +7 -0
- data/spec/dummy/config/routes.rb +29 -1
- data/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb +1 -1
- data/spec/dummy/db/migrate/20120524202412_create_doorkeeper_tables.rb +6 -4
- data/spec/dummy/db/schema.rb +5 -3
- data/spec/generators/application_owner_generator_spec.rb +23 -0
- data/spec/generators/install_generator_spec.rb +1 -6
- data/spec/generators/migration_generator_spec.rb +20 -0
- data/spec/lib/config_spec.rb +72 -4
- data/spec/lib/models/expirable_spec.rb +8 -11
- data/spec/lib/models/revocable_spec.rb +1 -1
- data/spec/lib/oauth/access_token_request_spec.rb +15 -9
- data/spec/lib/oauth/authorization_request_spec.rb +1 -0
- data/spec/lib/oauth/client_credentials_request_spec.rb +15 -9
- data/spec/lib/oauth/client_spec.rb +5 -8
- data/spec/lib/oauth/helpers/unique_token_spec.rb +2 -20
- data/spec/lib/oauth/password_access_token_request_spec.rb +16 -9
- data/spec/lib/oauth/token_spec.rb +83 -0
- data/spec/models/doorkeeper/access_token_spec.rb +41 -1
- data/spec/models/doorkeeper/application_spec.rb +53 -20
- data/spec/requests/flows/authorization_code_spec.rb +1 -1
- data/spec/requests/flows/client_credentials_spec.rb +2 -0
- data/spec/requests/flows/password_spec.rb +25 -0
- data/spec/requests/flows/refresh_token_spec.rb +5 -2
- data/spec/requests/protected_resources/private_api_spec.rb +10 -3
- data/spec/routing/custom_controller_routes_spec.rb +44 -0
- data/spec/routing/default_routes_spec.rb +32 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/spec_helper_integration.rb +18 -8
- data/spec/support/dependencies/factory_girl.rb +0 -3
- data/spec/support/orm/active_record.rb +11 -0
- data/spec/support/orm/mongoid.rb +26 -0
- data/spec/support/shared/controllers_shared_context.rb +2 -2
- data/spec/support/shared/models_shared_examples.rb +16 -0
- data/spec/validators/redirect_uri_validator_spec.rb +40 -0
- metadata +61 -37
- data/app/helpers/doorkeeper/application_helper.rb +0 -4
- data/app/models/doorkeeper/application.rb +0 -54
- data/config/routes.rb +0 -9
- data/lib/tasks/doorkeeper_tasks.rake +0 -4
- data/spec/support/dependencies/database_cleaner.rb +0 -16
@@ -1,28 +1,28 @@
|
|
1
1
|
module Doorkeeper
|
2
|
-
class AccessGrant
|
2
|
+
class AccessGrant
|
3
3
|
include Doorkeeper::OAuth::Helpers
|
4
4
|
include Doorkeeper::Models::Expirable
|
5
5
|
include Doorkeeper::Models::Revocable
|
6
|
+
include Doorkeeper::Models::Accessible
|
6
7
|
include Doorkeeper::Models::Scopes
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
belongs_to :application
|
9
|
+
belongs_to :application, :class_name => "Doorkeeper::Application"
|
11
10
|
|
12
11
|
attr_accessible :resource_owner_id, :application_id, :expires_in, :redirect_uri, :scopes
|
13
12
|
|
14
13
|
validates :resource_owner_id, :application_id, :token, :expires_in, :redirect_uri, :presence => true
|
14
|
+
validates :token, :uniqueness => true
|
15
15
|
|
16
16
|
before_validation :generate_token, :on => :create
|
17
17
|
|
18
|
-
def
|
19
|
-
|
18
|
+
def self.authenticate(token)
|
19
|
+
where(:token => token).first
|
20
20
|
end
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def generate_token
|
25
|
-
self.token = UniqueToken.
|
25
|
+
self.token = UniqueToken.generate
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -1,17 +1,16 @@
|
|
1
1
|
module Doorkeeper
|
2
|
-
class AccessToken
|
2
|
+
class AccessToken
|
3
3
|
include Doorkeeper::OAuth::Helpers
|
4
4
|
include Doorkeeper::Models::Expirable
|
5
5
|
include Doorkeeper::Models::Revocable
|
6
|
+
include Doorkeeper::Models::Accessible
|
6
7
|
include Doorkeeper::Models::Scopes
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
belongs_to :application
|
11
|
-
|
12
|
-
scope :accessible, where(:revoked_at => nil)
|
9
|
+
belongs_to :application, :class_name => "Doorkeeper::Application"
|
13
10
|
|
14
11
|
validates :application_id, :token, :presence => true
|
12
|
+
validates :token, :uniqueness => true
|
13
|
+
validates :refresh_token, :uniqueness => true, :if => :use_refresh_token?
|
15
14
|
|
16
15
|
attr_accessor :use_refresh_token
|
17
16
|
attr_accessible :application_id, :resource_owner_id, :expires_in, :scopes, :use_refresh_token
|
@@ -19,47 +18,51 @@ module Doorkeeper
|
|
19
18
|
before_validation :generate_token, :on => :create
|
20
19
|
before_validation :generate_refresh_token, :on => :create, :if => :use_refresh_token?
|
21
20
|
|
21
|
+
def self.authenticate(token)
|
22
|
+
where(:token => token).first
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.by_refresh_token(refresh_token)
|
26
|
+
where(:refresh_token => refresh_token).first
|
27
|
+
end
|
28
|
+
|
22
29
|
def self.revoke_all_for(application_id, resource_owner)
|
23
30
|
where(:application_id => application_id,
|
24
31
|
:resource_owner_id => resource_owner.id).delete_all
|
25
32
|
end
|
26
33
|
|
27
34
|
def self.matching_token_for(application, resource_owner_or_id, scopes)
|
28
|
-
|
35
|
+
resource_owner_id = resource_owner_or_id.respond_to?(:to_key) ? resource_owner_or_id.id : resource_owner_or_id
|
36
|
+
token = last_authorized_token_for(application, resource_owner_id)
|
29
37
|
token if token && ScopeChecker.matches?(token.scopes, scopes)
|
30
38
|
end
|
31
39
|
|
32
|
-
def self.last_authorized_token_for(application, resource_owner_or_id)
|
33
|
-
resource_owner_id = resource_owner_or_id.kind_of?(ActiveRecord::Base) ? resource_owner_or_id.id : resource_owner_or_id
|
34
|
-
accessible.
|
35
|
-
where(:application_id => application.id,
|
36
|
-
:resource_owner_id => resource_owner_id).
|
37
|
-
order("created_at desc").
|
38
|
-
limit(1).
|
39
|
-
first
|
40
|
-
end
|
41
|
-
private_class_method :last_authorized_token_for
|
42
|
-
|
43
40
|
def token_type
|
44
41
|
"bearer"
|
45
42
|
end
|
46
43
|
|
47
|
-
def accessible?
|
48
|
-
!expired? && !revoked?
|
49
|
-
end
|
50
|
-
|
51
44
|
def use_refresh_token?
|
52
45
|
self.use_refresh_token
|
53
46
|
end
|
54
47
|
|
48
|
+
def as_json(options={})
|
49
|
+
{
|
50
|
+
:resource_owner_id => self.resource_owner_id,
|
51
|
+
:scopes => self.scopes,
|
52
|
+
:expires_in_seconds => self.expires_in_seconds,
|
53
|
+
:application => { :uid => self.application.uid }
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
55
57
|
private
|
56
58
|
|
57
59
|
def generate_refresh_token
|
58
|
-
|
60
|
+
write_attribute :refresh_token, UniqueToken.generate
|
59
61
|
end
|
60
62
|
|
61
63
|
def generate_token
|
62
|
-
self.token = UniqueToken.
|
64
|
+
self.token = UniqueToken.generate
|
63
65
|
end
|
66
|
+
|
64
67
|
end
|
65
68
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Doorkeeper
|
2
|
+
class AccessToken < ActiveRecord::Base
|
3
|
+
self.table_name = :oauth_access_tokens
|
4
|
+
|
5
|
+
def self.last_authorized_token_for(application, resource_owner_id)
|
6
|
+
where(:application_id => application.id,
|
7
|
+
:resource_owner_id => resource_owner_id,
|
8
|
+
:revoked_at => nil).
|
9
|
+
order('created_at desc').
|
10
|
+
limit(1).
|
11
|
+
first
|
12
|
+
end
|
13
|
+
private_class_method :last_authorized_token_for
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Doorkeeper
|
2
|
+
class Application < ActiveRecord::Base
|
3
|
+
self.table_name = :oauth_applications
|
4
|
+
|
5
|
+
has_many :authorized_tokens, :class_name => "AccessToken", :conditions => { :revoked_at => nil }
|
6
|
+
has_many :authorized_applications, :through => :authorized_tokens, :source => :application
|
7
|
+
|
8
|
+
def self.column_names_with_table
|
9
|
+
self.column_names.map { |c| "oauth_applications.#{c}" }
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.authorized_for(resource_owner)
|
13
|
+
joins(:authorized_applications).
|
14
|
+
where(:oauth_access_tokens => { :resource_owner_id => resource_owner.id, :revoked_at => nil }).
|
15
|
+
group(column_names_with_table.join(','))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Doorkeeper
|
2
|
+
class Application
|
3
|
+
include Doorkeeper::OAuth::Helpers
|
4
|
+
|
5
|
+
has_many :access_grants, :dependent => :destroy, :class_name => "Doorkeeper::AccessGrant"
|
6
|
+
has_many :access_tokens, :dependent => :destroy, :class_name => "Doorkeeper::AccessToken"
|
7
|
+
|
8
|
+
validates :name, :secret, :uid, :redirect_uri, :presence => true
|
9
|
+
validates :uid, :uniqueness => true
|
10
|
+
validates :redirect_uri, :redirect_uri => true
|
11
|
+
|
12
|
+
before_validation :generate_uid, :generate_secret, :on => :create
|
13
|
+
|
14
|
+
attr_accessible :name, :redirect_uri
|
15
|
+
|
16
|
+
def self.model_name
|
17
|
+
ActiveModel::Name.new(self, Doorkeeper, 'Application')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.authenticate(uid, secret)
|
21
|
+
self.where(:uid => uid, :secret => secret).first
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.by_uid(uid)
|
25
|
+
self.where(:uid => uid).first
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def generate_uid
|
31
|
+
self.uid = UniqueToken.generate
|
32
|
+
end
|
33
|
+
|
34
|
+
def generate_secret
|
35
|
+
self.secret = UniqueToken.generate
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -5,13 +5,15 @@ module Doorkeeper
|
|
5
5
|
expires_in && Time.now > expired_time
|
6
6
|
end
|
7
7
|
|
8
|
-
def time_left
|
9
|
-
expired? ? 0 : expired_time - Time.now
|
10
|
-
end
|
11
|
-
|
12
8
|
def expired_time
|
13
9
|
created_at + expires_in.seconds
|
14
10
|
end
|
11
|
+
|
12
|
+
def expires_in_seconds
|
13
|
+
expires = (created_at + expires_in.seconds) - Time.now
|
14
|
+
expires_sec = expires.seconds.round(0)
|
15
|
+
expires_sec > 0 ? expires_sec : 0
|
16
|
+
end
|
15
17
|
private :expired_time
|
16
18
|
end
|
17
19
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'doorkeeper/models/mongoid/revocable'
|
2
|
+
require 'doorkeeper/models/mongoid/scopes'
|
3
|
+
|
4
|
+
module Doorkeeper
|
5
|
+
class AccessGrant
|
6
|
+
include Mongoid::Document
|
7
|
+
include Mongoid::Timestamps
|
8
|
+
include Doorkeeper::Models::Mongoid::Revocable
|
9
|
+
include Doorkeeper::Models::Mongoid::Scopes
|
10
|
+
|
11
|
+
self.store_in :oauth_access_grants
|
12
|
+
|
13
|
+
field :resource_owner_id, :type => Hash
|
14
|
+
field :application_id, :type => Hash
|
15
|
+
field :token, :type => String
|
16
|
+
field :expires_in, :type => Integer
|
17
|
+
field :redirect_uri, :type => String
|
18
|
+
field :revoked_at, :type => DateTime
|
19
|
+
|
20
|
+
index :token, :unique => true
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'doorkeeper/models/mongoid/revocable'
|
2
|
+
require 'doorkeeper/models/mongoid/scopes'
|
3
|
+
|
4
|
+
module Doorkeeper
|
5
|
+
class AccessToken
|
6
|
+
include Mongoid::Document
|
7
|
+
include Mongoid::Timestamps
|
8
|
+
include Doorkeeper::Models::Mongoid::Revocable
|
9
|
+
include Doorkeeper::Models::Mongoid::Scopes
|
10
|
+
|
11
|
+
self.store_in :oauth_access_tokens
|
12
|
+
|
13
|
+
field :resource_owner_id, :type => Integer
|
14
|
+
field :token, :type => String
|
15
|
+
field :expires_in, :type => Integer
|
16
|
+
field :revoked_at, :type => DateTime
|
17
|
+
|
18
|
+
index :token, :unique => true
|
19
|
+
index :refresh_token, :unique => true, :sparse => true
|
20
|
+
|
21
|
+
def self.last_authorized_token_for(application, resource_owner_id)
|
22
|
+
where(:application_id => application.id,
|
23
|
+
:resource_owner_id => resource_owner_id,
|
24
|
+
:revoked_at => nil).
|
25
|
+
order_by([:created_at, :desc]).
|
26
|
+
limit(1).
|
27
|
+
first
|
28
|
+
end
|
29
|
+
private_class_method :last_authorized_token_for
|
30
|
+
|
31
|
+
def refresh_token
|
32
|
+
self[:refresh_token]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Doorkeeper
|
2
|
+
class Application
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
|
6
|
+
self.store_in :oauth_applications
|
7
|
+
|
8
|
+
has_many :authorized_tokens, :class_name => "Doorkeeper::AccessToken"
|
9
|
+
|
10
|
+
field :name, :type => String
|
11
|
+
field :uid, :type => String
|
12
|
+
field :secret, :type => String
|
13
|
+
field :redirect_uri, :type => String
|
14
|
+
|
15
|
+
index :uid, :unique => true
|
16
|
+
|
17
|
+
def self.authorized_for(resource_owner)
|
18
|
+
ids = AccessToken.where(:resource_owner_id => resource_owner.id, :revoked_at => nil).map(&:application_id)
|
19
|
+
find(ids)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Doorkeeper
|
2
|
+
module Models
|
3
|
+
module Ownership
|
4
|
+
def validate_owner?
|
5
|
+
Doorkeeper.configuration.confirm_application_owner?
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
belongs_to :owner, :polymorphic => true
|
11
|
+
validates :owner, :presence => true, :if => :validate_owner?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,12 +1,16 @@
|
|
1
1
|
module Doorkeeper
|
2
2
|
module Models
|
3
3
|
module Scopes
|
4
|
-
def
|
5
|
-
|
6
|
-
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
define_method :scopes do
|
7
|
+
Doorkeeper::OAuth::Scopes.from_string(self[:scopes])
|
8
|
+
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
+
define_method :scopes_string do
|
11
|
+
Doorkeeper::OAuth::Scopes.from_string(self[:scopes]).to_s
|
12
|
+
end
|
13
|
+
end
|
10
14
|
end
|
11
15
|
end
|
12
16
|
end
|
@@ -81,11 +81,11 @@ module Doorkeeper::OAuth
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def token_via_authorization_code
|
84
|
-
Doorkeeper::AccessGrant.
|
84
|
+
Doorkeeper::AccessGrant.authenticate(code)
|
85
85
|
end
|
86
86
|
|
87
87
|
def token_via_refresh_token
|
88
|
-
Doorkeeper::AccessToken.
|
88
|
+
Doorkeeper::AccessToken.by_refresh_token(refresh_token)
|
89
89
|
end
|
90
90
|
|
91
91
|
def create_access_token
|
@@ -4,8 +4,6 @@ module Doorkeeper
|
|
4
4
|
class Code
|
5
5
|
include URIBuilder
|
6
6
|
|
7
|
-
DEFAULT_EXPIRATION_TIME = 600
|
8
|
-
|
9
7
|
attr_accessor :authorization, :grant
|
10
8
|
|
11
9
|
def initialize(authorization)
|
@@ -16,7 +14,7 @@ module Doorkeeper
|
|
16
14
|
@grant ||= AccessGrant.create!(
|
17
15
|
:application_id => authorization.client.id,
|
18
16
|
:resource_owner_id => authorization.resource_owner.id,
|
19
|
-
:expires_in =>
|
17
|
+
:expires_in => configuration.authorization_code_expires_in,
|
20
18
|
:redirect_uri => authorization.redirect_uri,
|
21
19
|
:scopes => authorization.scopes.to_s
|
22
20
|
)
|
@@ -28,6 +26,10 @@ module Doorkeeper
|
|
28
26
|
:state => authorization.state
|
29
27
|
})
|
30
28
|
end
|
29
|
+
|
30
|
+
def configuration
|
31
|
+
Doorkeeper.configuration
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|