mauth-client 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +13 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +119 -0
- data/CONTRIBUTING.md +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +19 -0
- data/README.md +235 -0
- data/Rakefile +7 -0
- data/doc/implementations.md +6 -0
- data/doc/mauth-client_CLI.md +59 -0
- data/doc/mauth-proxy.md +71 -0
- data/doc/mauth.yml.md +73 -0
- data/examples/Gemfile +4 -0
- data/examples/Gemfile.lock +41 -0
- data/examples/README.md +33 -0
- data/examples/config.yml +8 -0
- data/examples/get_user_info.rb +58 -0
- data/examples/mauth_key +0 -0
- data/exe/mauth-client +264 -0
- data/exe/mauth-proxy +40 -0
- data/lib/mauth/autoload.rb +8 -0
- data/lib/mauth/client.rb +487 -0
- data/lib/mauth/core_ext.rb +7 -0
- data/lib/mauth/dice_bag/mauth.rb.dice +12 -0
- data/lib/mauth/dice_bag/mauth.yml.dice +14 -0
- data/lib/mauth/dice_bag/mauth_key.dice +1 -0
- data/lib/mauth/dice_bag/mauth_templates.rb +19 -0
- data/lib/mauth/fake/rack.rb +45 -0
- data/lib/mauth/faraday.rb +87 -0
- data/lib/mauth/middleware.rb +23 -0
- data/lib/mauth/proxy.rb +77 -0
- data/lib/mauth/rack.rb +137 -0
- data/lib/mauth/request_and_response.rb +73 -0
- data/lib/mauth/version.rb +3 -0
- data/lib/mauth-client.rb +1 -0
- data/lib/rack/mauth.rb +1 -0
- data/mauth-client.gemspec +36 -0
- metadata +292 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
mauth-client (3.1.4)
|
5
|
+
coderay (~> 1.0)
|
6
|
+
dice_bag (>= 0.9, < 2.0)
|
7
|
+
faraday (~> 0.7)
|
8
|
+
faraday_middleware (~> 0.9)
|
9
|
+
rack
|
10
|
+
term-ansicolor (~> 1.0)
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: https://rubygems.org/
|
14
|
+
specs:
|
15
|
+
coderay (1.1.1)
|
16
|
+
dice_bag (1.1.1)
|
17
|
+
diff-lcs (~> 1.0)
|
18
|
+
rake
|
19
|
+
thor (~> 0.0)
|
20
|
+
diff-lcs (1.2.5)
|
21
|
+
faraday (0.9.2)
|
22
|
+
multipart-post (>= 1.2, < 3)
|
23
|
+
faraday_middleware (0.10.0)
|
24
|
+
faraday (>= 0.7.4, < 0.10)
|
25
|
+
multipart-post (2.0.0)
|
26
|
+
rack (2.0.1)
|
27
|
+
rake (11.2.2)
|
28
|
+
term-ansicolor (1.3.2)
|
29
|
+
tins (~> 1.0)
|
30
|
+
thor (0.19.1)
|
31
|
+
tins (1.11.0)
|
32
|
+
|
33
|
+
PLATFORMS
|
34
|
+
ruby
|
35
|
+
|
36
|
+
DEPENDENCIES
|
37
|
+
faraday (~> 0.9.0)
|
38
|
+
mauth-client!
|
39
|
+
|
40
|
+
BUNDLED WITH
|
41
|
+
1.12.5
|
data/examples/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Examples
|
2
|
+
|
3
|
+
## Configuration
|
4
|
+
|
5
|
+
After obtaining valid credentials you need to edit the `config.yml` file and set the `app_uuid` accordingly.
|
6
|
+
You also need to provide a mauth key and put it in the `mauth_key` file.
|
7
|
+
See [the mauth config file doc](../doc/mauth.yml.md) for more information.
|
8
|
+
|
9
|
+
This folder contains its own Gemfile and Gemfile.lock files to manage dependencies so you need to run
|
10
|
+
```
|
11
|
+
bundle install
|
12
|
+
```
|
13
|
+
before trying any of the scripts.
|
14
|
+
|
15
|
+
|
16
|
+
## Fetching a given user's info
|
17
|
+
|
18
|
+
Simply run the provided shell script by passing an user's UUID, for instance:
|
19
|
+
```
|
20
|
+
./get_user_info.rb 4735d013-8d78-4980-8846-fbecf0db0b8e
|
21
|
+
```
|
22
|
+
|
23
|
+
This should print the user's info, something along the lines of:
|
24
|
+
```
|
25
|
+
{
|
26
|
+
"user": {
|
27
|
+
"login": "name",
|
28
|
+
"email": "the.email.address@example.com",
|
29
|
+
"uuid": "4735d013-8d78-4980-8846-fbecf0db0b8e",
|
30
|
+
...
|
31
|
+
}
|
32
|
+
}
|
33
|
+
```
|
data/examples/config.yml
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
abort "USAGE: ./#{__FILE__} <USER UUID>" unless ARGV.size == 1
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
Bundler.require(:default)
|
7
|
+
|
8
|
+
def config
|
9
|
+
@conf ||= YAML.load(File.open("./config.yml"))
|
10
|
+
end
|
11
|
+
|
12
|
+
# get user information
|
13
|
+
def get_user_info_mauth(user_uuid)
|
14
|
+
get_data_from_imedidata "users/#{user_uuid}.json"
|
15
|
+
end
|
16
|
+
|
17
|
+
# fetch data from iMedidata
|
18
|
+
def get_data_from_imedidata(resource_name)
|
19
|
+
puts "fetching #{resource_name}..."
|
20
|
+
begin
|
21
|
+
connection = Faraday::Connection.new(url: config["imedidata"]["host"]) do |builder|
|
22
|
+
builder.use MAuth::Faraday::RequestSigner, config["mauth"]
|
23
|
+
builder.adapter Faraday.default_adapter
|
24
|
+
end
|
25
|
+
|
26
|
+
# get the data
|
27
|
+
response = connection.get "/api/v2/#{resource_name}"
|
28
|
+
puts "HTTP #{response.status}"
|
29
|
+
|
30
|
+
# return the user info
|
31
|
+
if response.status == 200
|
32
|
+
result = JSON.parse(response.body)
|
33
|
+
puts JSON.pretty_generate(result)
|
34
|
+
result
|
35
|
+
else
|
36
|
+
puts response.body
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
rescue JSON::ParserError => e
|
40
|
+
puts "Error parsing data from imedidata: #{e.inspect}"
|
41
|
+
puts e.backtrace.join("\n")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
get_user_info_mauth(ARGV[0])
|
46
|
+
|
47
|
+
|
48
|
+
### OTHER EXAMPLES
|
49
|
+
|
50
|
+
#### get study groups for an user
|
51
|
+
def get_study_groups_mauth(user_uuid)
|
52
|
+
get_data_from_imedidata "users/#{user_uuid}/study_groups.json"
|
53
|
+
end
|
54
|
+
|
55
|
+
#### get roles for a user in an application study
|
56
|
+
def get_user_study_roles_mauth(user_uuid, study_uuid)
|
57
|
+
get_data_from_imedidata "users/#{user_uuid}/studies/#{study_uuid}/apps/#{config["mauth"]["app_uuid"]}/roles.json"
|
58
|
+
end
|
data/examples/mauth_key
ADDED
File without changes
|
data/exe/mauth-client
ADDED
@@ -0,0 +1,264 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../lib', File.dirname(__FILE__))
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'faraday'
|
7
|
+
require 'faraday_middleware'
|
8
|
+
require 'logger'
|
9
|
+
require 'mauth/client'
|
10
|
+
require 'mauth/faraday'
|
11
|
+
require 'yaml'
|
12
|
+
require 'term/ansicolor'
|
13
|
+
|
14
|
+
# OPTION PARSER
|
15
|
+
|
16
|
+
require 'optparse'
|
17
|
+
|
18
|
+
# $options default values
|
19
|
+
$options = {
|
20
|
+
:authenticate_response => true,
|
21
|
+
:verbose => true,
|
22
|
+
:color => nil,
|
23
|
+
:no_ssl_verify => false,
|
24
|
+
}
|
25
|
+
|
26
|
+
additional_headers = []
|
27
|
+
opt_parser = OptionParser.new do |opts|
|
28
|
+
opts.banner = "Usage: mauth-client [options] <verb> <url> [body]"
|
29
|
+
|
30
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely - output is like curl -v (this is the default)") do |v|
|
31
|
+
$options[:verbose] = v
|
32
|
+
end
|
33
|
+
opts.on("-q", "Run quietly - only outputs the response body (same as --no-verbose)") do |v|
|
34
|
+
$options[:verbose] = !v
|
35
|
+
end
|
36
|
+
opts.on("--[no-]authenticate", "Authenticate the response received") do |v|
|
37
|
+
$options[:authenticate_response] = v
|
38
|
+
end
|
39
|
+
opts.on("--[no-]color", "Color the output (defaults to color if the output device is a TTY)") do |v|
|
40
|
+
$options[:color] = v
|
41
|
+
end
|
42
|
+
opts.on("-t", "--content-type CONTENT-TYPE", "Sets the Content-Type header of the request") do |v|
|
43
|
+
$options[:content_type] = v
|
44
|
+
end
|
45
|
+
opts.on("-H", "--header LINE", "accepts a json string of additional headers to included. IE 'cache-expirey: 10, other: value") do |v|
|
46
|
+
additional_headers << v
|
47
|
+
end
|
48
|
+
opts.on("--no-ssl-verify", "Disables SSL verification - use cautiously!") do
|
49
|
+
$options[:no_ssl_verify] = true
|
50
|
+
end
|
51
|
+
$options[:additional_headers] =additional_headers
|
52
|
+
end
|
53
|
+
opt_parser.parse!
|
54
|
+
abort(opt_parser.help) unless (2..3).include?(ARGV.size)
|
55
|
+
|
56
|
+
# FIND MAUTH CONFIG
|
57
|
+
|
58
|
+
possible_mauth_config_files = [
|
59
|
+
# whoops, I called this MAUTH_CONFIG_YML in one place and MAUTH_CONFIG_YAML in another. supporting both for now.
|
60
|
+
ENV['MAUTH_CONFIG_YML'],
|
61
|
+
ENV['MAUTH_CONFIG_YAML'],
|
62
|
+
'~/.mauth_config.yml',
|
63
|
+
'./config/mauth.yml',
|
64
|
+
'./mauth.yml',
|
65
|
+
].compact
|
66
|
+
|
67
|
+
mauth_config_yml = possible_mauth_config_files.detect do |filename|
|
68
|
+
File.exists?(File.expand_path(filename))
|
69
|
+
end
|
70
|
+
unless mauth_config_yml
|
71
|
+
message = "could not find mauth config. giving up. please place a mauth config in one of the standard places, " +
|
72
|
+
"or point the MAUTH_CONFIG_YML environment variable at an existing one. standard places are:" +
|
73
|
+
possible_mauth_config_files.map{|f| "\n\t#{f}" }.join
|
74
|
+
abort message
|
75
|
+
end
|
76
|
+
|
77
|
+
mauth_config = MAuth::Client.default_config(:mauth_config_yml => File.expand_path(mauth_config_yml))
|
78
|
+
|
79
|
+
# INSTANTIATE MAUTH CLIENT
|
80
|
+
|
81
|
+
logger = Logger.new(STDERR)
|
82
|
+
mauth_client = MAuth::Client.new(mauth_config.merge('logger' => logger))
|
83
|
+
|
84
|
+
# OUTPUTTERS FOR FARADAY THAT SHOULD MOVE TO A LIB SOMEWHERE
|
85
|
+
|
86
|
+
# outputs the response body to the given output device (defaulting to STDOUT)
|
87
|
+
class FaradayOutputter < Faraday::Middleware
|
88
|
+
def initialize(app, outdev=STDOUT)
|
89
|
+
@app=app
|
90
|
+
@outdev = outdev
|
91
|
+
end
|
92
|
+
|
93
|
+
def call(request_env)
|
94
|
+
@app.call(request_env).on_complete do |response_env|
|
95
|
+
@outdev.puts(response_env[:body] || '')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# this is to approximate `curl -v`s output. but it's all faked, whereas curl gives you
|
101
|
+
# the real text written and read for request and response. whatever, close enough.
|
102
|
+
class FaradayCurlVOutputter < FaradayOutputter
|
103
|
+
|
104
|
+
# defines a method with the given name, applying coloring defined by any additional arguments.
|
105
|
+
# if $options[:color] is set, respects that; otherwise, applies color if the output device is a tty.
|
106
|
+
def self.color(name, *color_args)
|
107
|
+
define_method(name) do |arg|
|
108
|
+
if color?
|
109
|
+
color_args.inject(arg) do |result, color_arg|
|
110
|
+
Term::ANSIColor.send(color_arg, result)
|
111
|
+
end
|
112
|
+
else
|
113
|
+
arg
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
color :info, :intense_yellow
|
119
|
+
color :info_body, :yellow
|
120
|
+
color :protocol
|
121
|
+
|
122
|
+
color :request, :intense_cyan
|
123
|
+
color :request_verb, :bold
|
124
|
+
color :request_header
|
125
|
+
color :request_blankline, :intense_cyan, :bold
|
126
|
+
|
127
|
+
color :response, :intense_green
|
128
|
+
color :response_status, :bold, :green
|
129
|
+
color :response_header
|
130
|
+
color :response_blankline, :intense_green, :bold
|
131
|
+
|
132
|
+
def call(request_env)
|
133
|
+
@outdev.puts "#{info('*')} #{info_body("connect to #{request_env[:url].host} on port #{request_env[:url].port}")}"
|
134
|
+
@outdev.puts "#{info('*')} #{info_body("getting our SSL on")}" if request_env[:url].scheme=='https'
|
135
|
+
@outdev.puts "#{request('>')} #{request_verb(request_env[:method].to_s.upcase)} #{request_env[:url].path} #{protocol('HTTP/1.1' || 'or something - TODO')}"
|
136
|
+
request_env[:request_headers].each do |k, v|
|
137
|
+
@outdev.puts "#{request('>')} #{request_header(k)}#{request(':')} #{v}"
|
138
|
+
end
|
139
|
+
@outdev.puts "#{request_blankline('>')} "
|
140
|
+
request_body = color_body_by_content_type(request_env[:body], request_env[:request_headers]['Content-Type'])
|
141
|
+
(request_body || '').split("\n", -1).each do |line|
|
142
|
+
@outdev.puts "#{request('>')} #{line}"
|
143
|
+
end
|
144
|
+
@app.call(request_env).on_complete do |response_env|
|
145
|
+
@outdev.puts "#{response('<')} #{protocol('HTTP/1.1' || 'or something - TODO')} #{response_status(response_env[:status].to_s)}"
|
146
|
+
request_env[:response_headers].each do |k, v|
|
147
|
+
@outdev.puts "#{response('<')} #{response_header(k)}#{response(':')} #{v}"
|
148
|
+
end
|
149
|
+
@outdev.puts "#{response_blankline ('<')} "
|
150
|
+
response_body = color_body_by_content_type(response_env[:body], response_env[:response_headers]['Content-Type'])
|
151
|
+
(response_body || '').split("\n", -1).each do |line|
|
152
|
+
@outdev.puts "#{response('<')} #{line}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# whether to use color
|
158
|
+
def color?
|
159
|
+
$options[:color].nil? ? @outdev.tty? : $options[:color]
|
160
|
+
end
|
161
|
+
|
162
|
+
# a mapping for each registered CodeRay scanner to the Media Types which represent
|
163
|
+
# that language. extremely incomplete!
|
164
|
+
CodeRayForMediaTypes = {
|
165
|
+
:c => [],
|
166
|
+
:cpp => [],
|
167
|
+
:clojure => [],
|
168
|
+
:css => ['text/css', 'application/css-stylesheet'],
|
169
|
+
:delphi => [],
|
170
|
+
:diff => [],
|
171
|
+
:erb => [],
|
172
|
+
:groovy => [],
|
173
|
+
:haml => [],
|
174
|
+
:html => ['text/html'],
|
175
|
+
:java => [],
|
176
|
+
:java_script => ['application/javascript', 'text/javascript', 'application/x-javascript'],
|
177
|
+
:json => ['application/json', %r(\Aapplication/.*\+json\z)],
|
178
|
+
:php => [],
|
179
|
+
:python => ['text/x-python'],
|
180
|
+
:ruby => [],
|
181
|
+
:sql => [],
|
182
|
+
:xml => ['text/xml', 'application/xml', %r(\Aapplication/.*\+xml\z)],
|
183
|
+
:yaml => [],
|
184
|
+
}
|
185
|
+
|
186
|
+
# takes a body and a content type; returns the body, with coloring (ansi colors for terminals)
|
187
|
+
# possibly added, if it's a recognized content type and #color? is true
|
188
|
+
def color_body_by_content_type(body, content_type)
|
189
|
+
if body && color?
|
190
|
+
# kinda hacky way to get the media_type. faraday should supply this ...
|
191
|
+
require 'rack'
|
192
|
+
media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
|
193
|
+
coderay_scanner = CodeRayForMediaTypes.reject{|k,v| !v.any?{|type| type === media_type} }.keys.first
|
194
|
+
if coderay_scanner
|
195
|
+
require 'coderay'
|
196
|
+
if coderay_scanner == :json
|
197
|
+
body = begin
|
198
|
+
JSON.pretty_generate(JSON.parse(body))
|
199
|
+
rescue JSON::ParserError
|
200
|
+
body
|
201
|
+
end
|
202
|
+
end
|
203
|
+
body = CodeRay.scan(body, coderay_scanner).encode(:terminal)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
body
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# CONFIGURE THE FARADAY CONNECTION
|
211
|
+
faraday_options = {}
|
212
|
+
if $options[:no_ssl_verify]
|
213
|
+
faraday_options[:ssl] = {:verify => false}
|
214
|
+
end
|
215
|
+
connection = Faraday.new(faraday_options) do |builder|
|
216
|
+
builder.use MAuth::Faraday::MAuthClientUserAgent, "MAuth-Client CLI"
|
217
|
+
builder.use MAuth::Faraday::RequestSigner, :mauth_client => mauth_client
|
218
|
+
if $options[:authenticate_response]
|
219
|
+
builder.use MAuth::Faraday::ResponseAuthenticator, :mauth_client => mauth_client
|
220
|
+
end
|
221
|
+
builder.use $options[:verbose] ? FaradayCurlVOutputter : FaradayOutputter
|
222
|
+
builder.adapter Faraday.default_adapter
|
223
|
+
end
|
224
|
+
|
225
|
+
httpmethod, url, body = *ARGV
|
226
|
+
|
227
|
+
unless Faraday::Connection::METHODS.map{|m| m.to_s.downcase }.include?(httpmethod.downcase)
|
228
|
+
abort "Unrecognized HTTP method given: #{httpmethod}\n\n" + opt_parser.help
|
229
|
+
end
|
230
|
+
|
231
|
+
headers = {}
|
232
|
+
if $options[:content_type]
|
233
|
+
headers['Content-Type'] = $options[:content_type]
|
234
|
+
else
|
235
|
+
if body
|
236
|
+
# I'd rather not have a default content-type, but if none is set then the HTTP adapter sets this to
|
237
|
+
# application/x-www-form-urlencoded anyway. application/json is a better default for our purposes.
|
238
|
+
headers['Content-Type'] = 'application/json'
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
if $options[:additional_headers]
|
243
|
+
$options[:additional_headers].each do |cur|
|
244
|
+
raise "Headers must be in the format of [key]:[value]" unless cur.include?(':')
|
245
|
+
key, throw_away ,value = cur.partition(':')
|
246
|
+
headers[key] = value
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# OH LOOK IT'S FINALLY ACTUALLY CONNECTING TO SOMETHING
|
251
|
+
|
252
|
+
begin
|
253
|
+
response = connection.run_request(httpmethod.downcase.to_sym, url, body, headers)
|
254
|
+
rescue MAuth::InauthenticError, MAuth::UnableToAuthenticateError => e
|
255
|
+
if $options[:color].nil? ? STDERR.tty? : $options[:color]
|
256
|
+
class_color = Term::ANSIColor.method(e.is_a?(MAuth::InauthenticError) ? :intense_red : :intense_yellow)
|
257
|
+
message_color = Term::ANSIColor.method(e.is_a?(MAuth::InauthenticError) ? :red : :yellow)
|
258
|
+
else
|
259
|
+
class_color = proc{|s| s }
|
260
|
+
message_color = proc{|s| s }
|
261
|
+
end
|
262
|
+
STDERR.puts(class_color.call(e.class.to_s))
|
263
|
+
STDERR.puts(message_color.call(e.message))
|
264
|
+
end
|
data/exe/mauth-proxy
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../lib', File.dirname(__FILE__))
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'mauth/proxy'
|
7
|
+
require 'rack'
|
8
|
+
|
9
|
+
headers = []
|
10
|
+
headers_index = ARGV.find_index('--header')
|
11
|
+
while(headers_index) do
|
12
|
+
headers << ARGV[headers_index + 1]
|
13
|
+
ARGV.delete_at(headers_index + 1)
|
14
|
+
ARGV.delete_at(headers_index)
|
15
|
+
headers_index = ARGV.find_index('--header')
|
16
|
+
end
|
17
|
+
|
18
|
+
authenticate_responses = !ARGV.delete('--no-authenticate')
|
19
|
+
browser_proxy = !(ARGV.delete('--browser_proxy').nil?)
|
20
|
+
|
21
|
+
target_uri = browser_proxy ? ARGV : ARGV.pop
|
22
|
+
|
23
|
+
if !target_uri || target_uri.empty?
|
24
|
+
abort("Usage: mauth-proxy [rack options] --browser_proxy [--no-authenticate] <target URI> <target URI> ...\n" +
|
25
|
+
"or: mauth-proxy [rack options] <target URI>")
|
26
|
+
end
|
27
|
+
|
28
|
+
rack_server_options = Rack::Server::Options.new.parse!(ARGV)
|
29
|
+
|
30
|
+
# for security, this rack server will only accept local connections, so override Host
|
31
|
+
# to 127.0.0.1 (from the default of 0.0.0.0)
|
32
|
+
#
|
33
|
+
# this means that the '-o' / '--host' option to Rack::Server::Options is ignored.
|
34
|
+
rack_server_options[:Host] = "127.0.0.1"
|
35
|
+
|
36
|
+
rack_server_options[:app] = MAuth::Proxy.new(target_uri, :authenticate_responses => authenticate_responses,
|
37
|
+
:browser_proxy => browser_proxy, :headers => headers)
|
38
|
+
|
39
|
+
mauth_proxy_server = Rack::Server.new(rack_server_options)
|
40
|
+
mauth_proxy_server.start
|