cyclid 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|