cassette 1.0.18 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -12
  3. data/lib/cassette/authentication/authorities.rb +34 -30
  4. data/lib/cassette/authentication/cache.rb +22 -18
  5. data/lib/cassette/authentication/filter.rb +52 -33
  6. data/lib/cassette/authentication/user.rb +20 -16
  7. data/lib/cassette/authentication.rb +39 -27
  8. data/lib/cassette/cache.rb +2 -2
  9. data/lib/cassette/client/cache.rb +39 -35
  10. data/lib/cassette/client.rb +5 -4
  11. data/lib/cassette/http/parsed_response.rb +20 -0
  12. data/lib/cassette/http/request.rb +44 -0
  13. data/lib/cassette/http/ticket_response.rb +48 -0
  14. data/lib/cassette/http.rb +8 -0
  15. data/lib/cassette/rubycas/helper.rb +2 -8
  16. data/lib/cassette/rubycas/routing_constraint.rb +23 -0
  17. data/lib/cassette/rubycas/single_sign_out_constraint.rb +8 -8
  18. data/lib/cassette/rubycas/user_factory.rb +14 -0
  19. data/lib/cassette/rubycas.rb +2 -0
  20. data/lib/cassette/version.rb +2 -2
  21. data/lib/cassette.rb +12 -50
  22. data/spec/cassette/authentication/authorities_spec.rb +1 -1
  23. data/spec/cassette/authentication/cache_spec.rb +40 -4
  24. data/spec/cassette/authentication/filter_spec.rb +106 -36
  25. data/spec/cassette/authentication/user_factory_spec.rb +42 -0
  26. data/spec/cassette/authentication/user_spec.rb +4 -3
  27. data/spec/cassette/authentication_spec.rb +24 -12
  28. data/spec/cassette/cache_spec.rb +0 -2
  29. data/spec/cassette/client/cache_spec.rb +1 -1
  30. data/spec/cassette/client_spec.rb +319 -0
  31. data/spec/cassette/errors_spec.rb +1 -1
  32. data/spec/cassette/http/parsed_response_spec.rb +27 -0
  33. data/spec/cassette/http/request_spec.rb +41 -0
  34. data/spec/cassette/http/ticket_response_spec.rb +41 -0
  35. data/spec/cassette/rubycas/routing_constraint_spec.rb +84 -0
  36. data/spec/cassette_spec.rb +36 -0
  37. data/spec/integration/cas/client_spec.rb +0 -3
  38. data/spec/spec_helper.rb +5 -0
  39. data/spec/support/controllers/controller_mock.rb +19 -0
  40. metadata +98 -36
  41. data/spec/cas_spec.rb +0 -78
@@ -0,0 +1,44 @@
1
+ module Cassette
2
+ module Http
3
+ module Request
4
+ extend self
5
+
6
+ def post(uri, payload, timeout = DEFAULT_TIMEOUT)
7
+ perform(:post, uri, timeout) do |request|
8
+ request.body = URI.encode_www_form(payload)
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def perform(http_verb, uri, timeout, &block)
15
+ request(uri, timeout)
16
+ .tap(&log_request)
17
+ .public_send(http_verb, &block)
18
+ .tap(&check_response)
19
+ end
20
+
21
+ def request(uri, timeout)
22
+ Faraday.new(url: uri, ssl: { verify: false, version: 'TLSv1' }) do |con|
23
+ con.adapter Faraday.default_adapter
24
+ con.options.timeout = timeout
25
+ end
26
+ end
27
+
28
+ def log_request
29
+ lambda { |request| Cassette.logger.debug "Request: #{request.inspect}" }
30
+ end
31
+
32
+ def check_response
33
+ lambda do |resp|
34
+ Cassette.logger.debug(
35
+ "Got response: #{resp.body.inspect} (#{resp.status}), " \
36
+ "#{resp.headers.inspect}"
37
+ )
38
+
39
+ Cassette::Errors.raise_by_code(resp.status) unless resp.success?
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,48 @@
1
+ module Cassette
2
+ module Http
3
+ class TicketResponse
4
+ def initialize(response)
5
+ @content = ParsedResponse.new(response)
6
+ end
7
+
8
+ def login
9
+ fetch_val(
10
+ content,
11
+ 'serviceResponse',
12
+ 'authenticationSuccess',
13
+ 'user',
14
+ '__content__'
15
+ )
16
+ end
17
+
18
+ def name
19
+ fetch_val(attributes, 'cn', '__content__')
20
+ end
21
+
22
+ def authorities
23
+ fetch_val(attributes, 'authorities', '__content__')
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :content
29
+
30
+ def fetch_val(hash, *keys)
31
+ keys.reduce(hash, &access_key)
32
+ end
33
+
34
+ def access_key
35
+ lambda { |hash, key| hash.try(:[], key) }
36
+ end
37
+
38
+ def attributes
39
+ fetch_val(
40
+ content,
41
+ 'serviceResponse',
42
+ 'authenticationSuccess',
43
+ 'attributes'
44
+ )
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'http/request'
2
+ require_relative 'http/parsed_response'
3
+ require_relative 'http/ticket_response'
4
+
5
+ module Cassette
6
+ module Http
7
+ end
8
+ end
@@ -6,6 +6,7 @@ module Cassette
6
6
  module Rubycas
7
7
  module Helper
8
8
  extend ActiveSupport::Concern
9
+ extend UserFactory
9
10
 
10
11
  included do
11
12
  before_filter :validate_authentication_ticket
@@ -60,14 +61,7 @@ module Cassette
60
61
  return fake_user if ENV['NOAUTH']
61
62
  return nil unless session[:cas_user]
62
63
 
63
- @current_user ||= begin
64
- attributes = session[:cas_extra_attributes]
65
- Cassette::Authentication::User.new(login: session[:cas_user],
66
- name: attributes.try(:[], :cn),
67
- email: attributes.try(:[], :email),
68
- authorities: attributes.try(:[], :authorities),
69
- type: attributes.try(:[], :type).try(:downcase))
70
- end
64
+ @current_user ||= from_session(session)
71
65
  end
72
66
  end
73
67
  end
@@ -0,0 +1,23 @@
1
+ module Cassette
2
+ module Rubycas
3
+ class RoutingConstraint
4
+ include UserFactory
5
+
6
+ attr_reader :role, :options
7
+
8
+ def initialize(role, opts = {})
9
+ defaults = { raw: false }
10
+ @role = role
11
+ @options = defaults.merge(opts)
12
+ end
13
+
14
+ def matches?(request)
15
+ user = from_session(request.session)
16
+
17
+ meth = options[:raw] ? :has_raw_role? : :has_role?
18
+
19
+ user.send(meth, role)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,18 +3,18 @@
3
3
  module Cassette
4
4
  module Rubycas
5
5
  class SingleSignOutConstraint
6
+ LOGOUT_PAYLOAD_EXPR = %r{<samlp:LogoutRequest.*?<samlp:SessionIndex>(.*)<\/samlp:SessionIndex}m
7
+
8
+ def logout_request?(params)
9
+ [params['logoutRequest'], URI.unescape(params['logoutRequest'])].find { |xml| xml =~ LOGOUT_PAYLOAD_EXPR }
10
+ end
11
+
6
12
  def matches?(request)
7
- if (content_type = request.headers['CONTENT_TYPE']) &&
8
- content_type =~ /^multipart\//
13
+ if (content_type = request.headers['CONTENT_TYPE']) && content_type =~ %r{^multipart/}
9
14
  return false
10
15
  end
11
16
 
12
- if request.post? &&
13
- request.request_parameters['logoutRequest'] &&
14
- [request.request_parameters['logoutRequest'],
15
- URI.unescape(request.request_parameters['logoutRequest'])]
16
- .find { |xml| xml =~ /^<samlp:LogoutRequest.*?<samlp:SessionIndex>(.*)<\/samlp:SessionIndex>/m }
17
-
17
+ if request.post? && request.request_parameters['logoutRequest'] && logout_request?(request.request_parameters)
18
18
  Cassette.logger.debug "Intercepted a single sign out request on #{request}"
19
19
  return true
20
20
  end
@@ -0,0 +1,14 @@
1
+ module Cassette
2
+ module Rubycas
3
+ module UserFactory
4
+ def from_session(session)
5
+ attributes = session[:cas_extra_attributes]
6
+ Cassette::Authentication::User.new(login: session[:cas_user],
7
+ name: attributes.try(:[], :cn),
8
+ email: attributes.try(:[], :email),
9
+ authorities: attributes.try(:[], :authorities),
10
+ type: attributes.try(:[], :type).try(:downcase))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,8 +1,10 @@
1
1
  # encoding: UTF-8
2
2
 
3
+ require 'cassette/rubycas/user_factory'
3
4
  require 'cassette/rubycas/helper'
4
5
  require 'cassette/rubycas/single_sign_out_constraint'
5
6
  require 'cassette/rubycas/not_single_sign_out_constraint'
7
+ require 'cassette/rubycas/routing_constraint'
6
8
 
7
9
  module Cassette
8
10
  module Rubycas
@@ -1,8 +1,8 @@
1
1
  module Cassette
2
2
  class Version
3
3
  MAJOR = '1'
4
- MINOR = '0'
5
- PATCH = '18'
4
+ MINOR = '1'
5
+ PATCH = '0'
6
6
 
7
7
  def self.version
8
8
  [MAJOR, MINOR, PATCH].join('.')
data/lib/cassette.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'cassette/errors'
4
4
  require 'cassette/cache'
5
+ require 'cassette/http'
5
6
  require 'cassette/client/cache'
6
7
  require 'cassette/client'
7
8
  require 'cassette/authentication'
@@ -16,62 +17,23 @@ require 'logger'
16
17
  module Cassette
17
18
  extend self
18
19
 
20
+ attr_writer :config, :logger
21
+
19
22
  DEFAULT_TIMEOUT = 10
20
23
 
21
24
  def logger
22
- @@logger ||= begin
23
- if defined?(Rails) && Rails.logger
24
- Rails.logger
25
- else
26
- Logger.new('/dev/null')
27
- end
28
- end
29
- end
30
-
31
- def logger=(logger)
32
- @@logger = logger
25
+ @logger ||= begin
26
+ if defined?(::Rails) && ::Rails.logger
27
+ ::Rails.logger
28
+ else
29
+ Logger.new('/dev/null')
30
+ end
31
+ end
33
32
  end
34
33
 
35
34
  def config
36
- if defined?(@@config)
37
- @@config
38
- end
39
- end
40
-
41
- def config=(config)
42
- @@config = config
43
- end
44
-
45
- def new_request(uri, timeout)
46
- Faraday.new(url: uri, ssl: { verify: false, version: 'TLSv1' }) do |builder|
47
- builder.adapter Faraday.default_adapter
48
- builder.options.timeout = timeout
49
- end
35
+ @config if defined?(@config)
50
36
  end
51
37
 
52
- def get(uri, payload, timeout = DEFAULT_TIMEOUT)
53
- perform(:get, uri, payload, timeout) do |req|
54
- req.params = payload
55
- logger.debug "Request: #{req.inspect}"
56
- end
57
- end
58
-
59
- def post(uri, payload, timeout = DEFAULT_TIMEOUT)
60
- perform(:post, uri, payload, timeout) do |req|
61
- req.body = URI.encode_www_form(payload)
62
- logger.debug "Request: #{req.inspect}"
63
- end
64
- end
65
-
66
- protected
67
-
68
- def perform(op, uri, _payload, timeout = DEFAULT_TIMEOUT, &block)
69
- request = new_request(uri, timeout)
70
- res = request.send(op, &block)
71
-
72
- res.tap do |response|
73
- logger.debug "Got response: #{response.body.inspect} (#{response.status}), #{response.headers.inspect}"
74
- Cassette::Errors.raise_by_code(response.status) unless response.success?
75
- end
76
- end
38
+ delegate :post, to: :'Http::Request'
77
39
  end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+
2
2
 
3
3
  describe Cassette::Authentication::Authorities do
4
4
  subject do
@@ -1,8 +1,44 @@
1
-
2
1
  # encoding: utf-8
3
2
 
4
- require 'spec_helper'
5
-
6
3
  describe Cassette::Authentication::Cache do
7
- pending
4
+ subject(:cache) { described_class.new(Logger.new('/dev/null')) }
5
+
6
+ describe '#fetch_authentication' do
7
+ subject(:fetch_authentication) do
8
+ cache.fetch_authentication(ticket, service, &block)
9
+ end
10
+
11
+ let(:second_call) do
12
+ cache.fetch_authentication(ticket, service, &other_block)
13
+ end
14
+ let(:call_with_other_service) do
15
+ cache.fetch_authentication(ticket, other_service, &other_block)
16
+ end
17
+
18
+ let(:ticket) { 'ticket' }
19
+
20
+ let(:service) { 'lala' }
21
+ let(:other_service) { 'popo' }
22
+
23
+ let(:block) { -> { 1 } }
24
+ let(:other_block) { -> { 2 } }
25
+
26
+
27
+ before { cache.fetch_authentication(ticket, service, &block) }
28
+
29
+ it { is_expected.to eq(1) }
30
+
31
+ context 'when for a second time' do
32
+ it { expect(second_call).to eq(1) }
33
+
34
+ it do
35
+ expect(other_block).not_to receive(:call)
36
+ second_call
37
+ end
38
+
39
+ context 'when calling with a different service' do
40
+ it { expect(call_with_other_service).to eq(2) }
41
+ end
42
+ end
43
+ end
8
44
  end
@@ -1,24 +1,12 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'spec_helper'
4
- require 'active_support/core_ext/hash/indifferent_access'
3
+
5
4
 
6
5
  describe Cassette::Authentication::Filter do
7
6
  before do
8
7
  allow(Cassette::Authentication).to receive(:validate_ticket)
9
8
  end
10
9
 
11
- class ControllerMock
12
- attr_accessor :params, :request, :current_user
13
- def self.before_filter(*); end
14
- include Cassette::Authentication::Filter
15
-
16
- def initialize(params = {}, headers = {})
17
- self.params = params.with_indifferent_access
18
- self.request = OpenStruct.new(headers: headers.with_indifferent_access)
19
- end
20
- end
21
-
22
10
  shared_context 'with NOAUTH' do
23
11
  before do
24
12
  ENV['NOAUTH'] = 'yes'
@@ -30,7 +18,7 @@ describe Cassette::Authentication::Filter do
30
18
  end
31
19
 
32
20
  describe '#validate_raw_role!' do
33
- let(:controller) { ControllerMock.new }
21
+ let(:controller) { ControllerMock(described_class).new }
34
22
  let(:current_user) { instance_double(Cassette::Authentication::User) }
35
23
 
36
24
  before do
@@ -64,7 +52,7 @@ describe Cassette::Authentication::Filter do
64
52
  end
65
53
 
66
54
  describe '#validate_role!' do
67
- let(:controller) { ControllerMock.new }
55
+ let(:controller) { ControllerMock(described_class).new }
68
56
  let(:current_user) { instance_double(Cassette::Authentication::User) }
69
57
 
70
58
  before do
@@ -97,7 +85,6 @@ describe Cassette::Authentication::Filter do
97
85
  end
98
86
  end
99
87
 
100
-
101
88
  describe '#validate_authentication_ticket' do
102
89
  shared_examples_for 'controller without authentication' do
103
90
  it 'does not validate tickets' do
@@ -113,14 +100,14 @@ describe Cassette::Authentication::Filter do
113
100
 
114
101
  it_behaves_like 'with NOAUTH' do
115
102
  context 'and no ticket' do
116
- let(:controller) { ControllerMock.new }
103
+ let(:controller) { ControllerMock(described_class).new }
117
104
 
118
105
  it_behaves_like 'controller without authentication'
119
106
  end
120
107
 
121
108
  context 'and a ticket header' do
122
109
  let(:controller) do
123
- ControllerMock.new({}, 'Service-Ticket' => 'le ticket')
110
+ ControllerMock(described_class).new({}, 'Service-Ticket' => 'le ticket')
124
111
  end
125
112
 
126
113
  it_behaves_like 'controller without authentication'
@@ -128,43 +115,126 @@ describe Cassette::Authentication::Filter do
128
115
 
129
116
  context 'and a ticket param' do
130
117
  let(:controller) do
131
- ControllerMock.new(ticket: 'le ticket')
118
+ ControllerMock(described_class).new(ticket: 'le ticket')
132
119
  end
133
120
 
134
121
  it_behaves_like 'controller without authentication'
135
122
  end
136
123
  end
137
124
 
138
- context 'with a ticket in the query string *AND* headers' do
125
+ context 'when accepts_authentication_service? returns false' do
139
126
  let(:controller) do
140
- ControllerMock.new({ 'ticket' => 'le other ticket' }, 'Service-Ticket' => 'le ticket')
127
+ ControllerMock(described_class).new(ticket: 'le ticket')
141
128
  end
142
129
 
143
- it 'should send only the header ticket to validation' do
144
- controller.validate_authentication_ticket
145
- expect(Cassette::Authentication).to have_received(:validate_ticket).with('le ticket', Cassette.config.service)
130
+ before do
131
+ expect(controller).to receive(:accepts_authentication_service?)
132
+ .with(Cassette.config.service) { false }
133
+ end
134
+
135
+ it 'raises a Cassette::Errors::Forbidden' do
136
+ expect { controller.validate_authentication_ticket }
137
+ .to raise_error(Cassette::Errors::Forbidden)
146
138
  end
147
139
  end
148
140
 
149
- context 'with a ticket in the query string' do
150
- let(:controller) do
151
- ControllerMock.new('ticket' => 'le ticket')
141
+ context 'when accepts_authentication_service? returns true' do
142
+ before do
143
+ expect(controller).to receive(:accepts_authentication_service?).with(anything) { true }
152
144
  end
153
145
 
154
- it 'should send the ticket to validation' do
155
- controller.validate_authentication_ticket
156
- expect(Cassette::Authentication).to have_received(:validate_ticket).with('le ticket', Cassette.config.service)
146
+ context 'with a ticket in the query string *AND* headers' do
147
+ let(:controller) do
148
+ ControllerMock(described_class).new({ 'ticket' => 'le other ticket' },
149
+ 'Service-Ticket' => 'le ticket')
150
+ end
151
+
152
+ it 'should send only the header ticket to validation' do
153
+ controller.validate_authentication_ticket
154
+ expect(Cassette::Authentication).to have_received(:validate_ticket).with('le ticket', Cassette.config.service)
155
+ end
156
+ end
157
+
158
+ context 'with a ticket in the query string' do
159
+ let(:controller) do
160
+ ControllerMock(described_class).new('ticket' => 'le ticket')
161
+ end
162
+
163
+ it 'should send the ticket to validation' do
164
+ controller.validate_authentication_ticket
165
+ expect(Cassette::Authentication).to have_received(:validate_ticket).with('le ticket', Cassette.config.service)
166
+ end
157
167
  end
168
+
169
+ context 'when #authentication_service is overriden' do
170
+ let(:controller) do
171
+ mod = Module.new do
172
+ def authentication_service
173
+ "subdomain.#{Cassette.config.service}"
174
+ end
175
+ end
176
+
177
+ ControllerMock(described_class, mod).new({}, 'Service-Ticket' => 'le ticket')
178
+ end
179
+
180
+ it 'validates with the overriden value and not the config' do
181
+ controller.validate_authentication_ticket
182
+
183
+ expect(Cassette::Authentication).to have_received(:validate_ticket)
184
+ .with('le ticket', "subdomain.#{Cassette.config.service}")
185
+ end
186
+ end
187
+
188
+ context 'with a ticket in the Service-Ticket header' do
189
+ let(:controller) do
190
+ ControllerMock(described_class).new({}, 'Service-Ticket' => 'le ticket')
191
+ end
192
+
193
+ it 'sends the ticket to validation' do
194
+ controller.validate_authentication_ticket
195
+
196
+ expect(Cassette::Authentication).to have_received(:validate_ticket)
197
+ .with('le ticket', Cassette.config.service)
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ describe '#accepts_authentication_service?' do
204
+ let(:controller) do
205
+ ControllerMock(described_class).new(ticket: 'le ticket')
158
206
  end
159
207
 
160
- context 'with a ticket in the Service-Ticket header' do
161
- let(:controller) do
162
- ControllerMock.new({}, 'Service-Ticket' => 'le ticket')
208
+ before do
209
+ allow(Cassette).to receive(:config) { config }
210
+ end
211
+
212
+ subject { controller.accepts_authentication_service?(service) }
213
+
214
+ context 'when config responds to #services' do
215
+ let(:subdomain) { "subdomain.acme.org" }
216
+ let(:not_related) { "acme.org" }
217
+
218
+ let(:config) do
219
+ OpenStruct.new(YAML.load_file('spec/config.yml').merge(services: [subdomain]))
163
220
  end
164
221
 
165
- it 'should send the ticket to validation' do
166
- controller.validate_authentication_ticket
167
- expect(Cassette::Authentication).to have_received(:validate_ticket).with('le ticket', Cassette.config.service)
222
+ context 'and the authentication service is included in the configuration' do
223
+ let(:service) { subdomain }
224
+
225
+ it { is_expected.to eq true }
226
+ end
227
+
228
+ context 'and the authentication service is Cassette.config.service' do
229
+ let(:service) { Cassette.config.service }
230
+
231
+ it { is_expected.to eq true }
232
+ end
233
+
234
+ context 'and the authentication service is not included in the configuration' do
235
+ let(:service) { not_related }
236
+
237
+ it { is_expected.to eq false }
168
238
  end
169
239
  end
170
240
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Cassette::Rubycas::UserFactory do
4
+ let(:mod) do
5
+ Module.new do
6
+ extend Cassette::Rubycas::UserFactory
7
+ extend self
8
+ end
9
+ end
10
+
11
+ describe '#from_session' do
12
+ let(:session) do
13
+ name = Faker.name
14
+
15
+ {
16
+ cas_user: Faker::Internet.user_name(name),
17
+ cas_extra_attributes: {
18
+ email: Faker::Internet.email(name),
19
+ type: 'Customer',
20
+ authorities: '[CASTEST_ADMIN]'
21
+ }
22
+ }
23
+ end
24
+
25
+ let(:attributes) do
26
+ session[:cas_extra_attributes]
27
+ end
28
+
29
+ subject do
30
+ mod.from_session(session)
31
+ end
32
+
33
+ context 'with default attributes' do
34
+ its(:login) { is_expected.to eq(session[:cas_user]) }
35
+ its(:name) { is_expected.to eq(attributes[:name]) }
36
+ its(:email) { is_expected.to eq(attributes[:email]) }
37
+ its(:type) { is_expected.to eq(attributes[:type].downcase) }
38
+ it { is_expected.to be_customer }
39
+ it { is_expected.not_to be_employee }
40
+ end
41
+ end
42
+ end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+
2
2
 
3
3
  describe Cassette::Authentication::User do
4
4
  let(:base_authority) do
@@ -20,13 +20,14 @@ describe Cassette::Authentication::User do
20
20
  expect(config).to receive(:base_authority).and_return('TESTAPI')
21
21
  expect(Cassette::Authentication::Authorities).to receive(:new).with('[CUSTOMERAPI, SAPI]', 'TESTAPI')
22
22
 
23
- Cassette::Authentication::User.new(login: 'john.doe', name: 'John Doe', authorities: '[CUSTOMERAPI, SAPI]', config: config)
23
+ Cassette::Authentication::User.new(login: 'john.doe', name: 'John Doe',
24
+ authorities: '[CUSTOMERAPI, SAPI]', config: config)
24
25
  end
25
26
  end
26
27
  end
27
28
 
28
29
  describe '#has_role?' do
29
- let (:user) do
30
+ let(:user) do
30
31
  Cassette::Authentication::User.new(login: 'john.doe', name: 'John Doe',
31
32
  authorities: "[#{base_authority}, SAPI, #{base_authority}_CREATE-USER]")
32
33
  end