cyclid 0.2.0
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.
- checksums.yaml +7 -0
- data/LICENSE +174 -0
- data/README.md +54 -0
- data/app/cyclid.rb +61 -0
- data/app/cyclid/config.rb +38 -0
- data/app/cyclid/controllers.rb +123 -0
- data/app/cyclid/controllers/auth.rb +34 -0
- data/app/cyclid/controllers/auth/token.rb +78 -0
- data/app/cyclid/controllers/health.rb +96 -0
- data/app/cyclid/controllers/organizations.rb +104 -0
- data/app/cyclid/controllers/organizations/collection.rb +134 -0
- data/app/cyclid/controllers/organizations/config.rb +128 -0
- data/app/cyclid/controllers/organizations/document.rb +135 -0
- data/app/cyclid/controllers/organizations/job.rb +266 -0
- data/app/cyclid/controllers/organizations/members.rb +145 -0
- data/app/cyclid/controllers/organizations/stages.rb +251 -0
- data/app/cyclid/controllers/users.rb +47 -0
- data/app/cyclid/controllers/users/collection.rb +131 -0
- data/app/cyclid/controllers/users/document.rb +133 -0
- data/app/cyclid/health_helpers.rb +40 -0
- data/app/cyclid/job.rb +3 -0
- data/app/cyclid/job/helpers.rb +67 -0
- data/app/cyclid/job/job.rb +164 -0
- data/app/cyclid/job/runner.rb +275 -0
- data/app/cyclid/job/stage.rb +67 -0
- data/app/cyclid/log_buffer.rb +104 -0
- data/app/cyclid/models.rb +3 -0
- data/app/cyclid/models/job_record.rb +25 -0
- data/app/cyclid/models/organization.rb +64 -0
- data/app/cyclid/models/plugin_config.rb +25 -0
- data/app/cyclid/models/stage.rb +42 -0
- data/app/cyclid/models/step.rb +29 -0
- data/app/cyclid/models/user.rb +60 -0
- data/app/cyclid/models/user_permissions.rb +28 -0
- data/app/cyclid/monkey_patches.rb +37 -0
- data/app/cyclid/plugin_registry.rb +75 -0
- data/app/cyclid/plugins.rb +125 -0
- data/app/cyclid/plugins/action.rb +48 -0
- data/app/cyclid/plugins/action/command.rb +89 -0
- data/app/cyclid/plugins/action/email.rb +207 -0
- data/app/cyclid/plugins/action/email/html.erb +58 -0
- data/app/cyclid/plugins/action/email/text.erb +13 -0
- data/app/cyclid/plugins/action/script.rb +90 -0
- data/app/cyclid/plugins/action/slack.rb +129 -0
- data/app/cyclid/plugins/action/slack/note.erb +5 -0
- data/app/cyclid/plugins/api.rb +195 -0
- data/app/cyclid/plugins/api/github.rb +111 -0
- data/app/cyclid/plugins/api/github/callback.rb +66 -0
- data/app/cyclid/plugins/api/github/methods.rb +201 -0
- data/app/cyclid/plugins/api/github/status.rb +67 -0
- data/app/cyclid/plugins/builder.rb +80 -0
- data/app/cyclid/plugins/builder/mist.rb +107 -0
- data/app/cyclid/plugins/dispatcher.rb +89 -0
- data/app/cyclid/plugins/dispatcher/local.rb +167 -0
- data/app/cyclid/plugins/provisioner.rb +40 -0
- data/app/cyclid/plugins/provisioner/debian.rb +90 -0
- data/app/cyclid/plugins/provisioner/ubuntu.rb +98 -0
- data/app/cyclid/plugins/source.rb +39 -0
- data/app/cyclid/plugins/source/git.rb +64 -0
- data/app/cyclid/plugins/transport.rb +63 -0
- data/app/cyclid/plugins/transport/ssh.rb +155 -0
- data/app/cyclid/sinatra/api_helpers.rb +66 -0
- data/app/cyclid/sinatra/auth_helpers.rb +127 -0
- data/app/cyclid/sinatra/warden/strategies/api_token.rb +62 -0
- data/app/cyclid/sinatra/warden/strategies/basic.rb +58 -0
- data/app/cyclid/sinatra/warden/strategies/hmac.rb +76 -0
- data/app/db.rb +51 -0
- data/bin/cyclid-db-init +107 -0
- data/db/schema.rb +92 -0
- data/lib/cyclid/app.rb +4 -0
- metadata +407 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Copyright 2016 Liqwyd Ltd.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
# Top level module for all of the core Cyclid code.
|
17
|
+
module Cyclid
|
18
|
+
# Module for the Cyclid API
|
19
|
+
module API
|
20
|
+
# Module for all Organization related API endpoints
|
21
|
+
module Organizations
|
22
|
+
# API endpoints for Organization members
|
23
|
+
# @api REST
|
24
|
+
module Members
|
25
|
+
# @!group Organizations
|
26
|
+
|
27
|
+
# @!method get_organizations_organization_members_member
|
28
|
+
# @overload GET /organizations/:organization/members/:username
|
29
|
+
# @macro rest
|
30
|
+
# @param [String] organization Name of the organization.
|
31
|
+
# @param [String] username Username of the member.
|
32
|
+
# Get the details of the specified user within the organization.
|
33
|
+
# @return The requested member.
|
34
|
+
# @return [404] The organization or user does not exist, or the user is not a member of
|
35
|
+
# the organization.
|
36
|
+
# @example Get the 'user1' user from the 'example' organization
|
37
|
+
# GET /organizations/example/members/user1 => {"id": 1,
|
38
|
+
# "username": "user1",
|
39
|
+
# "email":"test@example.com",
|
40
|
+
# "permissions":{
|
41
|
+
# "admin":true,
|
42
|
+
# "write":true,
|
43
|
+
# "read":true
|
44
|
+
# }}
|
45
|
+
|
46
|
+
# @!method put_organizations_organization_members_member
|
47
|
+
# @overload PUT /organizations/:name/members/:username
|
48
|
+
# @macro rest
|
49
|
+
# @param [String] organization Name of the organization.
|
50
|
+
# @param [String] username Username of the member.
|
51
|
+
# Modify the permissions of specified user within the organization.
|
52
|
+
# @param [JSON] body User permissions.
|
53
|
+
# @option body [Hash] permissions Permissions to apply for the user.
|
54
|
+
# @return [200] The member was modified successfully.
|
55
|
+
# @return [404] The user does not exist, or is not a member of the organization.
|
56
|
+
# @example Give the member 'user1' write & read permissions for the 'example' organization
|
57
|
+
# PUT /organizations/example/members/user1 <= {"permissions": {
|
58
|
+
# "admin":false,
|
59
|
+
# "write":true,
|
60
|
+
# "read":true
|
61
|
+
# }}
|
62
|
+
|
63
|
+
# @!endgroup
|
64
|
+
|
65
|
+
# Sinatra callback
|
66
|
+
# @private
|
67
|
+
def self.registered(app)
|
68
|
+
include Errors::HTTPErrors
|
69
|
+
|
70
|
+
# Get the details of the specified user within the organization.
|
71
|
+
app.get '/:username' do
|
72
|
+
authorized_for!(params[:name], Operations::READ)
|
73
|
+
|
74
|
+
org = Organization.find_by(name: params[:name])
|
75
|
+
halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
|
76
|
+
if org.nil?
|
77
|
+
|
78
|
+
user = org.users.find_by(username: params[:username])
|
79
|
+
halt_with_json_response(404, INVALID_USER, 'user does not exist') \
|
80
|
+
if user.nil?
|
81
|
+
|
82
|
+
begin
|
83
|
+
perms = user.userpermissions.find_by(organization: org)
|
84
|
+
|
85
|
+
user_hash = user.serializable_hash
|
86
|
+
user_hash.delete_if do |key, _value|
|
87
|
+
key == 'password' || key == 'secret'
|
88
|
+
end
|
89
|
+
|
90
|
+
perms_hash = perms.serializable_hash
|
91
|
+
perms_hash.delete_if do |key, _value|
|
92
|
+
key == 'id' || key == 'user_id' || key == 'organization_id'
|
93
|
+
end
|
94
|
+
|
95
|
+
user_hash['permissions'] = perms_hash
|
96
|
+
|
97
|
+
return user_hash.to_json
|
98
|
+
rescue ActiveRecord::ActiveRecordError, \
|
99
|
+
ActiveRecord::UnknownAttributeError => ex
|
100
|
+
|
101
|
+
Cyclid.logger.debug ex.message
|
102
|
+
halt_with_json_response(500, INTERNAL_ERROR, ex.message)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Modify the specified user within the organization.
|
107
|
+
app.put '/:username' do
|
108
|
+
authorized_for!(params[:name], Operations::WRITE)
|
109
|
+
|
110
|
+
payload = parse_request_body
|
111
|
+
Cyclid.logger.debug payload
|
112
|
+
|
113
|
+
org = Organization.find_by(name: params[:name])
|
114
|
+
halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
|
115
|
+
if org.nil?
|
116
|
+
|
117
|
+
user = org.users.find_by(username: params[:username])
|
118
|
+
halt_with_json_response(404, INVALID_USER, 'user does not exist') \
|
119
|
+
if user.nil?
|
120
|
+
|
121
|
+
begin
|
122
|
+
perms = user.userpermissions.find_by(organization: org)
|
123
|
+
|
124
|
+
payload_perms = payload['permissions'] if payload.key? 'permissions'
|
125
|
+
unless payload_perms.nil?
|
126
|
+
perms.admin = payload_perms['admin'] if payload_perms.key? 'admin'
|
127
|
+
perms.write = payload_perms['write'] if payload_perms.key? 'write'
|
128
|
+
perms.read = payload_perms['read'] if payload_perms.key? 'read'
|
129
|
+
|
130
|
+
Cyclid.logger.debug perms.serializable_hash
|
131
|
+
|
132
|
+
perms.save!
|
133
|
+
end
|
134
|
+
rescue ActiveRecord::ActiveRecordError, \
|
135
|
+
ActiveRecord::UnknownAttributeError => ex
|
136
|
+
|
137
|
+
Cyclid.logger.debug ex.message
|
138
|
+
halt_with_json_response(500, INTERNAL_ERROR, ex.message)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Copyright 2016 Liqwyd Ltd.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
# Top level module for all of the core Cyclid code.
|
17
|
+
module Cyclid
|
18
|
+
# Module for the Cyclid API
|
19
|
+
module API
|
20
|
+
# Module for all Organization related API endpoints
|
21
|
+
module Organizations
|
22
|
+
# API endpoints for Organization Stages
|
23
|
+
# @api REST
|
24
|
+
module Stages
|
25
|
+
# @!group Organizations
|
26
|
+
|
27
|
+
# @!method get_organizations_organization_stages
|
28
|
+
# @overload GET /organizations/:organization/stages
|
29
|
+
# @macro rest
|
30
|
+
# @param [String] organization Name of the organization.
|
31
|
+
# Get all of the stages.
|
32
|
+
# @return All the stages within the organization.
|
33
|
+
# @return [404] The organization does not exist
|
34
|
+
|
35
|
+
# @!method post_organizations_organization_stages(body)
|
36
|
+
# @overload POST /organizations/:organization/stages
|
37
|
+
# @macro rest
|
38
|
+
# @param [String] organization Name of the organization.
|
39
|
+
# Create a new stage.
|
40
|
+
# @param [JSON] body New stage
|
41
|
+
# @option body [String] name Name of the new stage
|
42
|
+
# @option body [String] version (0.0.1) Version of the new stage
|
43
|
+
# @option body [Array<Object>] steps List of steps for the stage
|
44
|
+
# @return [400] The definition for the stage is invalid.
|
45
|
+
# @return [404] The organization does not exist.
|
46
|
+
# @return [409] A stage with the same name & version already exists.
|
47
|
+
# @example Create a simple stage for 'example' organization
|
48
|
+
# POST /organizations/example/stages <= {"name": "example",
|
49
|
+
# "steps": [
|
50
|
+
# {
|
51
|
+
# "action": "command",
|
52
|
+
# "cmd": "bundle install"
|
53
|
+
# }
|
54
|
+
# ]}
|
55
|
+
|
56
|
+
# @!method get_organizations_organization_stages_stage
|
57
|
+
# @overload GET /organizations/:organization/stages/:stage
|
58
|
+
# @macro rest
|
59
|
+
# @param [String] organization Name of the organization.
|
60
|
+
# @param [String] stage Name of the stage.
|
61
|
+
# Get the details of the specified stage within the organization. Returns every defined
|
62
|
+
# version for the given stage.
|
63
|
+
# @return The requested stage.
|
64
|
+
# @return [404] The organization or stage does not exist
|
65
|
+
|
66
|
+
# @!method get_organizations_organization_stages_stage_version
|
67
|
+
# @overload GET /organizations/:organization/stages/:stage/:version
|
68
|
+
# @macro rest
|
69
|
+
# @param [String] organization Name of the organization.
|
70
|
+
# @param [String] stage Name of the stage.
|
71
|
+
# @param [String] version Version of the stage.
|
72
|
+
# Get the details of the specified stage within the organization. Returns the specified
|
73
|
+
# version of the given stage.
|
74
|
+
# @return The requested stage.
|
75
|
+
# @return [404] The organization, stage or version of the stage does not exist
|
76
|
+
|
77
|
+
# @!endgroup
|
78
|
+
|
79
|
+
# Sinatra callback
|
80
|
+
# @private
|
81
|
+
def self.registered(app)
|
82
|
+
include Errors::HTTPErrors
|
83
|
+
|
84
|
+
# Get all of the stages.
|
85
|
+
app.get do
|
86
|
+
authorized_for!(params[:name], Operations::READ)
|
87
|
+
|
88
|
+
org = Organization.find_by(name: params[:name])
|
89
|
+
halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
|
90
|
+
if org.nil?
|
91
|
+
|
92
|
+
# Convert each Stage to a hash & sanitize it
|
93
|
+
stages = org.stages.all.map do |stage|
|
94
|
+
stage_hash = sanitize_stage(stage.serializable_hash)
|
95
|
+
|
96
|
+
# Santize each step in this stage
|
97
|
+
steps = stage.steps.map do |step|
|
98
|
+
sanitize_step(step.serializable_hash)
|
99
|
+
end
|
100
|
+
stage_hash['steps'] = steps
|
101
|
+
|
102
|
+
stage_hash
|
103
|
+
end
|
104
|
+
|
105
|
+
return stages.to_json
|
106
|
+
end
|
107
|
+
|
108
|
+
# Create a new stage.
|
109
|
+
app.post do
|
110
|
+
authorized_for!(params[:name], Operations::ADMIN)
|
111
|
+
|
112
|
+
payload = parse_request_body
|
113
|
+
Cyclid.logger.debug payload
|
114
|
+
|
115
|
+
org = Organization.find_by(name: params[:name])
|
116
|
+
halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
|
117
|
+
if org.nil?
|
118
|
+
|
119
|
+
halt_with_json_response(400, INVALID_JSON, 'stage does not define any steps') \
|
120
|
+
unless payload.key? 'steps'
|
121
|
+
|
122
|
+
# Ensure that a stage with the same name & version doesn't already exist
|
123
|
+
version = payload['version'] || '0.0.1'
|
124
|
+
halt_with_json_response(409, \
|
125
|
+
DUPLICATE, \
|
126
|
+
'A stage with that name and version already exists') \
|
127
|
+
if org.stages.exists?(name: payload['name'], version: version)
|
128
|
+
|
129
|
+
begin
|
130
|
+
stage = Stage.new
|
131
|
+
|
132
|
+
stage.name = payload['name']
|
133
|
+
stage.version = payload['version'] if payload.key? 'version'
|
134
|
+
stage.organization = org
|
135
|
+
|
136
|
+
# Create the steps & store their serialized form
|
137
|
+
stage.steps << create_steps(payload['steps'])
|
138
|
+
|
139
|
+
stage.save!
|
140
|
+
rescue ActiveRecord::ActiveRecordError, \
|
141
|
+
ActiveRecord::UnknownAttributeError => ex
|
142
|
+
|
143
|
+
Cyclid.logger.debug ex.message
|
144
|
+
halt_with_json_response(400, INVALID_JSON, ex.message)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns every defined version for the given stage.
|
149
|
+
app.get '/:stage' do
|
150
|
+
authorized_for!(params[:name], Operations::READ)
|
151
|
+
|
152
|
+
org = Organization.find_by(name: params[:name])
|
153
|
+
halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
|
154
|
+
if org.nil?
|
155
|
+
|
156
|
+
# There may be multiple versions of the same stage, so we need to
|
157
|
+
# find every instance of the given stage, convert each Stage to a
|
158
|
+
# hash & sanitize it
|
159
|
+
stages = org.stages.where(name: params[:stage]).map do |stage|
|
160
|
+
stage_hash = sanitize_stage(stage.serializable_hash)
|
161
|
+
|
162
|
+
# Santize each step in this stage
|
163
|
+
steps = stage.steps.map do |step|
|
164
|
+
sanitize_step(step.serializable_hash)
|
165
|
+
end
|
166
|
+
stage_hash['steps'] = steps
|
167
|
+
|
168
|
+
stage_hash
|
169
|
+
end
|
170
|
+
|
171
|
+
halt_with_json_response(404, INVALID_STAGE, 'stage does not exist') \
|
172
|
+
if stages.empty?
|
173
|
+
|
174
|
+
return stages.to_json
|
175
|
+
end
|
176
|
+
|
177
|
+
# Get the details of the specified stage within the organization.
|
178
|
+
# Returns the specified version of the given stage.
|
179
|
+
app.get '/:stage/:version' do
|
180
|
+
authorized_for!(params[:name], Operations::READ)
|
181
|
+
|
182
|
+
org = Organization.find_by(name: params[:name])
|
183
|
+
halt_with_json_response(404, INVALID_ORG, 'organization does not exist') \
|
184
|
+
if org.nil?
|
185
|
+
|
186
|
+
stage = org.stages.find_by(name: params[:stage], version: params[:version])
|
187
|
+
halt_with_json_response(404, INVALID_STAGE, 'stage does not exist') \
|
188
|
+
if stage.nil?
|
189
|
+
|
190
|
+
# Sanitize the stage
|
191
|
+
stage_hash = sanitize_stage(stage.serializable_hash)
|
192
|
+
|
193
|
+
# Santize each step in this stage
|
194
|
+
steps = stage.steps.map do |step|
|
195
|
+
sanitize_step(step.serializable_hash)
|
196
|
+
end
|
197
|
+
stage_hash['steps'] = steps
|
198
|
+
|
199
|
+
return stage_hash.to_json
|
200
|
+
end
|
201
|
+
|
202
|
+
app.helpers do
|
203
|
+
include Helpers
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Helpers for Stages
|
208
|
+
module Helpers
|
209
|
+
include Errors::HTTPErrors
|
210
|
+
|
211
|
+
# Create the serialized steps
|
212
|
+
#
|
213
|
+
# For each definition in the payload, inspect it and create the
|
214
|
+
# appropriate object for that action; that class is then serialized
|
215
|
+
# into JSON and stored in the Step in the database, and can then
|
216
|
+
# be unserialized back in to the desired object when it's needed
|
217
|
+
# without the database having to be aware of every single
|
218
|
+
# permutation of possible actions and arguments to them.
|
219
|
+
#
|
220
|
+
# @private
|
221
|
+
def create_steps(stage_steps)
|
222
|
+
sequence = 1
|
223
|
+
stage_steps.map do |stage_step|
|
224
|
+
step = Step.new
|
225
|
+
step.sequence = sequence
|
226
|
+
|
227
|
+
begin
|
228
|
+
action_name = stage_step['action']
|
229
|
+
plugin = Cyclid.plugins.find(action_name, Cyclid::API::Plugins::Action)
|
230
|
+
|
231
|
+
step_action = plugin.new(stage_step)
|
232
|
+
raise if step_action.nil?
|
233
|
+
rescue StandardError => ex
|
234
|
+
# XXX Rescue an internal exception
|
235
|
+
halt_with_json_response(404, INVALID_ACTION, ex.message)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Serialize the object into the Step and store it in the database.
|
239
|
+
step.action = Oj.dump(step_action)
|
240
|
+
step.save!
|
241
|
+
|
242
|
+
sequence += 1
|
243
|
+
|
244
|
+
step
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Copyright 2016 Liqwyd Ltd.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require_rel 'users/*.rb'
|
17
|
+
|
18
|
+
# Top level module for all of the core Cyclid code.
|
19
|
+
module Cyclid
|
20
|
+
# Module for the Cyclid API
|
21
|
+
module API
|
22
|
+
# Controller for all User related API endpoints
|
23
|
+
class UserController < ControllerBase
|
24
|
+
helpers do
|
25
|
+
# Remove sensitive data from the users data
|
26
|
+
def sanitize_user(user)
|
27
|
+
user.delete_if do |key, _value|
|
28
|
+
key == 'password' || key == 'secret'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
register Sinatra::Namespace
|
34
|
+
|
35
|
+
namespace '/users' do
|
36
|
+
register Users::Collection
|
37
|
+
|
38
|
+
namespace '/:username' do
|
39
|
+
register Users::Document
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Register this controller
|
45
|
+
Cyclid.controllers << UserController
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Copyright 2016 Liqwyd Ltd.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
# Top level module for all of the core Cyclid code.
|
17
|
+
module Cyclid
|
18
|
+
# Module for the Cyclid API
|
19
|
+
module API
|
20
|
+
# Module for all User related API endpoints
|
21
|
+
module Users
|
22
|
+
# API endpoints for the User collection
|
23
|
+
# @api REST
|
24
|
+
module Collection
|
25
|
+
# @!group Users
|
26
|
+
|
27
|
+
# @!method users
|
28
|
+
# @overload GET /users
|
29
|
+
# @macro rest
|
30
|
+
# Get all of the users.
|
31
|
+
# @return List of users
|
32
|
+
# @example Get a list of users
|
33
|
+
# GET /users => [{
|
34
|
+
# "id": 1,
|
35
|
+
# "username": "user1",
|
36
|
+
# "email": "user1@example.com"
|
37
|
+
# },
|
38
|
+
# {
|
39
|
+
# "id": 2,
|
40
|
+
# "username": "user2",
|
41
|
+
# "email": "user2@example.com"
|
42
|
+
# }]
|
43
|
+
# @see get_users_user
|
44
|
+
|
45
|
+
# @!method post_users(body)
|
46
|
+
# @overload POST /users
|
47
|
+
# @macro rest
|
48
|
+
# Create a new user. Note that only *one* of 'password' or 'new_password' should be
|
49
|
+
# passed.
|
50
|
+
# @param [JSON] body New user
|
51
|
+
# @option body [String] username Username of the new user
|
52
|
+
# @option body [String] name Users real name
|
53
|
+
# @option body [String] email Users email address
|
54
|
+
# @option body [String] password Bcrypt2 encrypted password
|
55
|
+
# @option body [String] new_password Password in plain text, which will be encrypted
|
56
|
+
# before being stored in the databaase.
|
57
|
+
# @option body [String] secret HMAC signing secret. This should be a suitably long
|
58
|
+
# random string.
|
59
|
+
# @return [200] User was created successfully
|
60
|
+
# @return [400] The user definition is invalid
|
61
|
+
# @return [409] An user with that name already exists
|
62
|
+
# @example Create a new user with an encrypted password
|
63
|
+
# POST /users <= {"username": "user1",
|
64
|
+
# "email": "user1@example.com",
|
65
|
+
# "password": "<Bcrypt2 encrypted password>"}
|
66
|
+
|
67
|
+
# @!endgroup
|
68
|
+
|
69
|
+
# Sinatra callback
|
70
|
+
# @private
|
71
|
+
def self.registered(app)
|
72
|
+
include Errors::HTTPErrors
|
73
|
+
|
74
|
+
# @macro [attach] sinatra.get
|
75
|
+
# @overload get "$1"
|
76
|
+
# @method get_users
|
77
|
+
# @return [String] JSON represention of all of all the users.
|
78
|
+
# Get all of the users across all organizations.
|
79
|
+
app.get do
|
80
|
+
authorized_admin!(Operations::READ)
|
81
|
+
|
82
|
+
# Retrieve the user data in a form we can more easily manipulate so
|
83
|
+
# that we can sanitize it
|
84
|
+
users = User.all_as_hash
|
85
|
+
|
86
|
+
# Remove any sensitive data
|
87
|
+
users.map! do |user|
|
88
|
+
sanitize_user(user)
|
89
|
+
end
|
90
|
+
|
91
|
+
return users.to_json
|
92
|
+
end
|
93
|
+
|
94
|
+
# @macro [attach] sinatra.post
|
95
|
+
# @overload post "$1"
|
96
|
+
# @method post_users
|
97
|
+
# Create a new user.
|
98
|
+
app.post do
|
99
|
+
authorized_admin!(Operations::ADMIN)
|
100
|
+
|
101
|
+
payload = parse_request_body
|
102
|
+
Cyclid.logger.debug payload
|
103
|
+
|
104
|
+
begin
|
105
|
+
halt_with_json_response(409, \
|
106
|
+
DUPLICATE, \
|
107
|
+
'a user with that name already exists') \
|
108
|
+
if User.exists?(username: payload['username'])
|
109
|
+
|
110
|
+
user = User.new
|
111
|
+
user.username = payload['username']
|
112
|
+
user.email = payload['email']
|
113
|
+
user.name = payload['name'] if payload.key? 'name'
|
114
|
+
user.password = payload['password'] if payload.key? 'password'
|
115
|
+
user.secret = payload['secret'] if payload.key? 'secret'
|
116
|
+
user.new_password = payload['new_password'] if payload.key? 'new_password'
|
117
|
+
user.save!
|
118
|
+
rescue ActiveRecord::ActiveRecordError, \
|
119
|
+
ActiveRecord::UnknownAttributeError => ex
|
120
|
+
|
121
|
+
Cyclid.logger.debug ex.message
|
122
|
+
halt_with_json_response(400, INVALID_JSON, ex.message)
|
123
|
+
end
|
124
|
+
|
125
|
+
return json_response(NO_ERROR, "user #{payload['username']} created")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|