From 880ce8405fc545f212d35aee198d4ea064e7582e Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Thu, 16 Jan 2020 14:50:53 -0800 Subject: [PATCH 01/18] speed up this migration during v2->v3 migration by skipping these huge tables resolves #972. --- .../0129-validation_result-result_order.sql | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sql/migrations/0129-validation_result-result_order.sql b/sql/migrations/0129-validation_result-result_order.sql index 417c4f937..33269d725 100644 --- a/sql/migrations/0129-validation_result-result_order.sql +++ b/sql/migrations/0129-validation_result-result_order.sql @@ -1,5 +1,19 @@ SELECT run_migration(129, $$ + -- In order to speed up deployment time (this migration file takes many + -- tens of hours to run on a production database), we drop all historical + -- validation_results. The overall outcome of all the validations is still + -- captured in validation_state.status. If it is desired to later load + -- that historical data back into the database, start with a backup of + -- production-v2, delete the following two lines from this file, and run + -- all migrations against that database, then copy the validation_state_member + -- and validation_result tables into the master database: + -- pg_dump -U postgres -t validation_result -t validation_state_member source_database | psql -U conch conch + + truncate validation_state_member; + truncate validation_result; + + -- these are the two validation modules that can produce duplicate -- results, with the exception of the result_order. For cpu_temperature -- at least, we can infer the component value; for switch_peers we cannot From 560b8d191556b906c78a779acfdef495489d2e7f Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Wed, 7 Aug 2019 16:53:16 -0700 Subject: [PATCH 02/18] enforce validation_result with a unique constraint This leverages work done in release v2.33.0 and .1 to make existing database entries unique. ACHTUNG!!! if validation_result is not empty, `bin/conch merge_validation_results` must have been run first!!! --- .../Conch::DB::Result::ValidationResult.md | 13 ++++++ lib/Conch/DB/Result/ValidationResult.pm | 42 ++++++++++++++++++- ...44-validation_result-unique-constraint.sql | 16 +++++++ sql/schema.sql | 22 ++++------ 4 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 sql/migrations/0144-validation_result-unique-constraint.sql diff --git a/docs/modules/Conch::DB::Result::ValidationResult.md b/docs/modules/Conch::DB::Result::ValidationResult.md index 3879879e0..59b8008c8 100644 --- a/docs/modules/Conch::DB::Result::ValidationResult.md +++ b/docs/modules/Conch::DB::Result::ValidationResult.md @@ -93,6 +93,19 @@ size: 16 - ["id"](#id) +# UNIQUE CONSTRAINTS + +## `validation_result_all_columns_key` + +- ["device\_id"](#device_id) +- ["hardware\_product\_id"](#hardware_product_id) +- ["validation\_id"](#validation_id) +- ["message"](#message) +- ["hint"](#hint) +- ["status"](#status) +- ["category"](#category) +- ["component"](#component) + # RELATIONS ## device diff --git a/lib/Conch/DB/Result/ValidationResult.pm b/lib/Conch/DB/Result/ValidationResult.pm index cfd2e8a55..86adf5851 100644 --- a/lib/Conch/DB/Result/ValidationResult.pm +++ b/lib/Conch/DB/Result/ValidationResult.pm @@ -143,6 +143,46 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key("id"); +=head1 UNIQUE CONSTRAINTS + +=head2 C + +=over 4 + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=back + +=cut + +__PACKAGE__->add_unique_constraint( + "validation_result_all_columns_key", + [ + "device_id", + "hardware_product_id", + "validation_id", + "message", + "hint", + "status", + "category", + "component", + ], +); + =head1 RELATIONS =head2 device @@ -221,7 +261,7 @@ __PACKAGE__->many_to_many( # Created by DBIx::Class::Schema::Loader v0.07049 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:+l8wIgJwgE/xE72CF/Dr4Q +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:cdTosC+PxDTHDZw4mNjetQ __PACKAGE__->add_columns( '+created' => { is_serializable => 0 }, diff --git a/sql/migrations/0144-validation_result-unique-constraint.sql b/sql/migrations/0144-validation_result-unique-constraint.sql new file mode 100644 index 000000000..c3438452d --- /dev/null +++ b/sql/migrations/0144-validation_result-unique-constraint.sql @@ -0,0 +1,16 @@ +SELECT run_migration(144, $$ + + -- We know that new validation results re-use old validation_result rows whenever possible + -- (see the end of Conch::ValidationSystem::run_validation_plan), and since release v3.0.0 + -- (where either: 1. merge_validation_results was run a final time, or 2. validation_result + -- was truncated) there are no duplicates, so it is now safe to turn this index into a + -- unique constraint. + + alter table validation_result + add constraint validation_result_all_columns_key unique + (device_id, hardware_product_id, validation_id, message, hint, status, category, component); + + drop index if exists validation_result_all_columns_idx; + drop index validation_result_device_id_idx; -- redundant with unique constraint + +$$); diff --git a/sql/schema.sql b/sql/schema.sql index 7c30d4c66..e04a6c728 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -1093,6 +1093,14 @@ ALTER TABLE ONLY public.validation_plan ADD CONSTRAINT validation_plan_pkey PRIMARY KEY (id); +-- +-- Name: validation_result validation_result_all_columns_key; Type: CONSTRAINT; Schema: public; Owner: conch +-- + +ALTER TABLE ONLY public.validation_result + ADD CONSTRAINT validation_result_all_columns_key UNIQUE (device_id, hardware_product_id, validation_id, message, hint, status, category, component); + + -- -- Name: validation_result validation_result_pkey; Type: CONSTRAINT; Schema: public; Owner: conch -- @@ -1442,20 +1450,6 @@ CREATE INDEX validation_plan_member_validation_plan_id_idx ON public.validation_ CREATE UNIQUE INDEX validation_plan_name_idx ON public.validation_plan USING btree (name) WHERE (deactivated IS NULL); --- --- Name: validation_result_all_columns_idx; Type: INDEX; Schema: public; Owner: conch --- - -CREATE INDEX validation_result_all_columns_idx ON public.validation_result USING btree (device_id, hardware_product_id, validation_id, message, hint, status, category, component); - - --- --- Name: validation_result_device_id_idx; Type: INDEX; Schema: public; Owner: conch --- - -CREATE INDEX validation_result_device_id_idx ON public.validation_result USING btree (device_id); - - -- -- Name: validation_result_hardware_product_id_idx; Type: INDEX; Schema: public; Owner: conch -- From b1b39d19e2b932d082bbd9d039b82f42ed8696c1 Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Fri, 10 Jan 2020 14:03:51 -0800 Subject: [PATCH 03/18] fix spelling test --- docs/modules/Conch::Plugin::DeprecatedAction.md | 2 +- lib/Conch/Plugin/DeprecatedAction.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/Conch::Plugin::DeprecatedAction.md b/docs/modules/Conch::Plugin::DeprecatedAction.md index f925c138e..92cdea581 100644 --- a/docs/modules/Conch::Plugin::DeprecatedAction.md +++ b/docs/modules/Conch::Plugin::DeprecatedAction.md @@ -12,7 +12,7 @@ Mojo plugin to detect and report the usage of deprecated controller actions. Sets the `X-Deprecated` header in the response. -Also sends a message to rollbar when a deprecated action is invoked, if the +Also sends a message to Rollbar when a deprecated action is invoked, if the `report_deprecated_actions` feature is enabled. # LICENSING diff --git a/lib/Conch/Plugin/DeprecatedAction.pm b/lib/Conch/Plugin/DeprecatedAction.pm index e18648e5b..a633b7fe0 100644 --- a/lib/Conch/Plugin/DeprecatedAction.pm +++ b/lib/Conch/Plugin/DeprecatedAction.pm @@ -19,7 +19,7 @@ Mojo plugin to detect and report the usage of deprecated controller actions. Sets the C header in the response. -Also sends a message to rollbar when a deprecated action is invoked, if the +Also sends a message to Rollbar when a deprecated action is invoked, if the C feature is enabled. =cut From 052ac98f685a680152be40ff9c804a2002bcbeae Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Mon, 13 Jan 2020 13:05:19 -0800 Subject: [PATCH 04/18] add POST /user/me ..to allow a user to update his own name or email address --- docs/modules/Conch::Route::User.md | 17 ++++++++++++++--- lib/Conch/Controller/User.pm | 19 +++++++++---------- lib/Conch/Route/User.pm | 26 +++++++++++++++++++++++--- t/integration/users.t | 28 +++++++++++++++++++++++++++- 4 files changed, 73 insertions(+), 17 deletions(-) diff --git a/docs/modules/Conch::Route::User.md b/docs/modules/Conch::Route::User.md index da52a8e54..a697634c4 100644 --- a/docs/modules/Conch::Route::User.md +++ b/docs/modules/Conch::Route::User.md @@ -14,6 +14,16 @@ All routes require authentication. - Response: [response.json#/definitions/UserDetailed](../json-schema/response.json#/definitions/UserDetailed) +### `POST /user/:target_user_id_or_email?send_mail=<1|0>` + +Optionally take the query parameter `send_mail` (defaults to `1`) to send +an email telling the user their account was updated + +- Request: [request.json#/definitions/UpdateUser](../json-schema/request.json#/definitions/UpdateUser) +- Success Response: Redirect to the user that was updated +- Error response on duplicate user: [response.json#/definitions/UserError](../json-schema/response.json#/definitions/UserError) (only if the +calling user is a system admin) + ### `POST /user/me/revoke?send_mail=<1|0>&login_only=<0|1>&api_only=<0|1>` Optionally accepts the following query parameters: @@ -84,18 +94,19 @@ otherwise, the user is logged out. ### `GET /user/:target_user_id_or_email` -- Requires system admin authorization +- Requires system admin authorization (when updating a different account than one's own) - Response: [response.json#/definitions/UserDetailed](../json-schema/response.json#/definitions/UserDetailed) ### `POST /user/:target_user_id_or_email?send_mail=<1|0>` Optionally take the query parameter `send_mail` (defaults to `1`) to send -an email telling the user their tokens were revoked +an email telling the user their account was updated - Requires system admin authorization - Request: [request.json#/definitions/UpdateUser](../json-schema/request.json#/definitions/UpdateUser) - Success Response: Redirect to the user that was updated -- Error response on duplicate user: [response.json#/definitions/UserError](../json-schema/response.json#/definitions/UserError) +- Error response on duplicate user: [response.json#/definitions/UserError](../json-schema/response.json#/definitions/UserError) (only if the +calling user is a system admin) ### `DELETE /user/:target_user_id_or_email?clear_tokens=<1|0>` diff --git a/lib/Conch/Controller/User.pm b/lib/Conch/Controller/User.pm index 0337c0927..10c98ce1c 100644 --- a/lib/Conch/Controller/User.pm +++ b/lib/Conch/Controller/User.pm @@ -385,6 +385,8 @@ sub update ($c) { return $c->status(400, { error => 'user email "'.$input->{email}.'" is not a valid RFC822 address' }) if exists $input->{email} and not Email::Valid->address($input->{email}); + my $is_system_admin = $c->is_system_admin; + my $user = $c->stash('target_user'); my %orig_columns = $user->get_columns; $user->set_columns($input); @@ -392,19 +394,16 @@ sub update ($c) { return $c->status(204) if not keys %dirty_columns; - if (exists $dirty_columns{email} and fc $input->{email} ne fc $orig_columns{email} - and my $dupe_user = $c->db_user_accounts->active->find_by_email($input->{email})) { - return $c->status(409, { - error => 'duplicate user found', - user => { map +($_ => $dupe_user->$_), qw(id email name created deactivated) }, - }); - } + return $c->status(403) if $dirty_columns{is_admin} and not $is_system_admin; - if (exists $dirty_columns{name} - and my $dupe_user = $c->db_user_accounts->active->search({ name => $input->{name} })->single) { + if (my $dupe_user = + (exists $dirty_columns{email} && (fc $input->{email} ne fc $orig_columns{email}) + && $c->db_user_accounts->active->find_by_email($input->{email})) + || (exists $dirty_columns{name} + && $c->db_user_accounts->active->search({ name => $input->{name} })->single) ) { return $c->status(409, { error => 'duplicate user found', - user => { map +($_ => $dupe_user->$_), qw(id email name created deactivated) }, + $is_system_admin ? ( user => { map +($_ => $dupe_user->$_), qw(id email name created deactivated) } ) : (), }); } diff --git a/lib/Conch/Route/User.pm b/lib/Conch/Route/User.pm index a6191f37b..3a389586e 100644 --- a/lib/Conch/Route/User.pm +++ b/lib/Conch/Route/User.pm @@ -32,6 +32,9 @@ sub routes { # GET /user/me $user_me->get('/')->to('#get'); + # POST /user/me?send_mail=<1|0> + $user_me->post('/')->to('#update'); + # POST /user/me/revoke?send_mail=<1|0>&login_only=<0|1>&api_only=<0|1> $user_me->post('/revoke')->to('#revoke_user_tokens'); @@ -136,6 +139,22 @@ All routes require authentication. =back +=head3 C<< POST /user/:target_user_id_or_email?send_mail=<1|0> >> + +Optionally take the query parameter C (defaults to C<1>) to send +an email telling the user their account was updated + +=over 4 + +=item * Request: F + +=item * Success Response: Redirect to the user that was updated + +=item * Error response on duplicate user: F (only if the +calling user is a system admin) + +=back + =head3 C<< POST /user/me/revoke?send_mail=<1|0>&login_only=<0|1>&api_only=<0|1> >> Optionally accepts the following query parameters: @@ -270,7 +289,7 @@ otherwise, the user is logged out. =over 4 -=item * Requires system admin authorization +=item * Requires system admin authorization (when updating a different account than one's own) =item * Response: F @@ -279,7 +298,7 @@ otherwise, the user is logged out. =head3 C<< POST /user/:target_user_id_or_email?send_mail=<1|0> >> Optionally take the query parameter C (defaults to C<1>) to send -an email telling the user their tokens were revoked +an email telling the user their account was updated =over 4 @@ -289,7 +308,8 @@ an email telling the user their tokens were revoked =item * Success Response: Redirect to the user that was updated -=item * Error response on duplicate user: F +=item * Error response on duplicate user: F (only if the +calling user is a system admin) =back diff --git a/t/integration/users.t b/t/integration/users.t index d0fae726c..ed97ea497 100644 --- a/t/integration/users.t +++ b/t/integration/users.t @@ -170,6 +170,32 @@ subtest 'User' => sub { TEST3 => 'test3', }); + $t->post_ok('/user/me', json => { is_admin => JSON::PP::true }) + ->status_is(403) + ->email_not_sent; + + $t->post_ok('/user/me', json => $_) + ->status_is(409) + ->json_is({ error => 'duplicate user found' }) + ->email_not_sent + foreach + { email => 'conch@conch.joyent.us' }, + { email => 'cONcH@cONCh.joyent.us' }, + { name => 'conch' }; + + $t->post_ok('/user/me', json => { email => 'rO_USer@cONCh.joyent.us', name => 'rO_USer' }) + ->status_is(303) + ->location_is('/user/'.$ro_user->id) + ->email_cmp_deeply({ + To => '"rO_USer" ', + From => 'noreply@127.0.0.1', + Subject => 'Your Conch account has been updated', + body => re(qr/^Your account at \Q$JOYENT\E has been updated:\R\R {7}email: ro_user\@conch.joyent.us -> rO_USer\@cONCh.joyent.us\R {8}name: ro_user -> rO_USer\R\R/m), + }); + + $ro_user->discard_changes; + + # re-authenticate as the same user $t->authenticate(email => $ro_user->email); my @login_token = ($t->tx->res->json->{jwt_token}); { @@ -314,7 +340,7 @@ subtest 'User' => sub { $t->post_ok('/login', json => { email => $ro_user->email, password => 'øƕḩẳȋ' }) ->status_is(200) - ->log_info_is('user ro_user ('.$ro_user->email.') logged in'); + ->log_info_is('user rO_USer ('.$ro_user->email.') logged in'); $t->post_ok('/user/me/password?clear_tokens=all', json => { password => 'another password' }) ->status_is(204, 'changed password again'); From 9052560d1c94d80e92e5f4237b1a942969440c45 Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Mon, 13 Jan 2020 14:21:45 -0800 Subject: [PATCH 05/18] clean up routing documentation --- docs/index.md | 8 +-- docs/modules/Conch::Route.md | 54 +++++++++++------- docs/modules/Conch::Route::Build.md | 36 ++++++------ docs/modules/Conch::Route::Datacenter.md | 18 +++--- docs/modules/Conch::Route::DatacenterRoom.md | 40 ++++++------- docs/modules/Conch::Route::Device.md | 56 +++++++++--------- docs/modules/Conch::Route::DeviceReport.md | 8 ++- docs/modules/Conch::Route::HardwareProduct.md | 14 +++-- docs/modules/Conch::Route::HardwareVendor.md | 12 ++-- docs/modules/Conch::Route::Organization.md | 16 +++--- docs/modules/Conch::Route::Rack.md | 30 +++++----- docs/modules/Conch::Route::RackLayout.md | 19 +++++-- docs/modules/Conch::Route::RackRole.md | 14 +++-- docs/modules/Conch::Route::Relay.md | 10 ++-- docs/modules/Conch::Route::Schema.md | 8 ++- docs/modules/Conch::Route::User.md | 50 ++++++++-------- docs/modules/Conch::Route::Validation.md | 16 +++--- docs/modules/Conch::Route::Workspace.md | 36 ++++++------ lib/Conch/Route.pm | 57 +++++++++++-------- lib/Conch/Route/Build.pm | 36 ++++++------ lib/Conch/Route/Datacenter.pm | 18 +++--- lib/Conch/Route/DatacenterRoom.pm | 40 ++++++------- lib/Conch/Route/Device.pm | 56 +++++++++--------- lib/Conch/Route/DeviceReport.pm | 8 ++- lib/Conch/Route/HardwareProduct.pm | 14 +++-- lib/Conch/Route/HardwareVendor.pm | 12 ++-- lib/Conch/Route/Organization.pm | 16 +++--- lib/Conch/Route/Rack.pm | 30 +++++----- lib/Conch/Route/RackLayout.pm | 19 +++++-- lib/Conch/Route/RackRole.pm | 14 +++-- lib/Conch/Route/Relay.pm | 10 ++-- lib/Conch/Route/Schema.pm | 8 ++- lib/Conch/Route/User.pm | 50 ++++++++-------- lib/Conch/Route/Validation.pm | 16 +++--- lib/Conch/Route/Workspace.pm | 36 ++++++------ 35 files changed, 490 insertions(+), 395 deletions(-) diff --git a/docs/index.md b/docs/index.md index 16ac55950..96961e2af 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,13 +35,13 @@ The majority of our endpoints consume and respond with JSON documents that conform to a set of JSON schema. These schema can be found in the [json-schema](json-schema) directory in the main repository, as well as on this documentation site. -Successful (http 2xx code) response structures are as described for each endpoint. +Successful (HTTP 2xx code) response structures are as described for each endpoint. Error responses will use: -- failure to validate query parameters: http 400, [response.json#/definitions/QueryParamsValidationError](json-schema/response.json#/definitions/QueryParamsValidationError) -- failure to validate request body payload: http 400, [response.json#/definitions/RequestValidationError](json-schema/response.json#/definitions/RequestValidationError) -- all other errors, unless specified: http 4xx, [response.json#/definitions/Error](json-schema/response.json#/definitions/Error) +- failure to validate query parameters: HTTP 400, [response.json#/definitions/QueryParamsValidationError](json-schema/response.json#/definitions/QueryParamsValidationError) +- failure to validate request body payload: HTTP 400, [response.json#/definitions/RequestValidationError](json-schema/response.json#/definitions/RequestValidationError) +- all other errors, unless specified: HTTP 4xx, [response.json#/definitions/Error](json-schema/response.json#/definitions/Error) Available routes are: diff --git a/docs/modules/Conch::Route.md b/docs/modules/Conch::Route.md index e8469832b..d1eb97a09 100644 --- a/docs/modules/Conch::Route.md +++ b/docs/modules/Conch::Route.md @@ -12,93 +12,103 @@ Set up all the routes for the Conch Mojo application. Set up the full route structure +# SHORTCUTS + +These are available on the root router. See ["Shortcuts" in Mojolicious::Guides::Routing](https://metacpan.org/pod/Mojolicious%3A%3AGuides%3A%3ARouting#shortcuts). + +## require\_system\_admin + +Chainable route that aborts with HTTP 403 if the user is not a system admin. + +# ROUTE ENDPOINTS + Unless otherwise specified, all routes require authentication. Full access is granted to system admin users, regardless of workspace, build or other role entries. -Successful (http 2xx code) response structures are as described for each endpoint. +Successful (HTTP 2xx code) response structures are as described for each endpoint. Error responses will use: -- failure to validate query parameters: http 400, [response.json#/definitions/QueryParamsValidationError](../json-schema/response.json#/definitions/QueryParamsValidationError) -- failure to validate request body payload: http 400, [response.json#/RequestValidationError](../json-schema/response.json#/RequestValidationError) -- all other errors, unless specified: http 4xx, [response.json#/Error](../json-schema/response.json#/Error) +- failure to validate query parameters: HTTP 400, [response.json#/definitions/QueryParamsValidationError](../json-schema/response.json#/definitions/QueryParamsValidationError) +- failure to validate request body payload: HTTP 400, [response.json#/RequestValidationError](../json-schema/response.json#/RequestValidationError) +- all other errors, unless specified: HTTP 4xx, [response.json#/Error](../json-schema/response.json#/Error) -### `GET /ping` +## `GET /ping` - Does not require authentication. - Response: [response.json#/definitions/Ping](../json-schema/response.json#/definitions/Ping) -### `GET /version` +## `GET /version` - Does not require authentication. - Response: [response.json#/definitions/Version](../json-schema/response.json#/definitions/Version) -### `POST /login` +## `POST /login` - Request: [request.json#/definitions/Login](../json-schema/request.json#/definitions/Login) - Response: [response.json#/definitions/Login](../json-schema/response.json#/definitions/Login) -### `POST /logout` +## `POST /logout` - Does not require authentication. - Response: `204 NO CONTENT` -### `GET /workspace/:workspace/device-totals` +## `GET /workspace/:workspace/device-totals` -### `GET /workspace/:workspace/device-totals.circ` +## `GET /workspace/:workspace/device-totals.circ` - Does not require authentication. - Response: [response.json#/definitions/DeviceTotals](../json-schema/response.json#/definitions/DeviceTotals) - Response (Circonus): [response.json#/definitions/DeviceTotalsCirconus](../json-schema/response.json#/definitions/DeviceTotalsCirconus) -### `POST /refresh_token` +## `POST /refresh_token` - Request: [request.json#/definitions/Null](../json-schema/request.json#/definitions/Null) - Response: [response.json#/definitions/Login](../json-schema/response.json#/definitions/Login) -### `* /dc`, `* /room`, `* /rack_role`, `* /rack`, `* /layout` +## `* /dc`, `* /room`, `* /rack_role`, `* /rack`, `* /layout` See ["routes" in Conch::Route::Datacenter](../modules/Conch%3A%3ARoute%3A%3ADatacenter#routes) -### `* /device` +## `* /device` See ["routes" in Conch::Route::Device](../modules/Conch%3A%3ARoute%3A%3ADevice#routes) -### `* /device_report` +## `* /device_report` See ["routes" in Conch::Route::DeviceReport](../modules/Conch%3A%3ARoute%3A%3ADeviceReport#routes) -### `* /hardware_product` +## `* /hardware_product` See ["routes" in Conch::Route::HardwareProduct](../modules/Conch%3A%3ARoute%3A%3AHardwareProduct#routes) -### `* /hardware_vendor` +## `* /hardware_vendor` See ["routes" in Conch::Route::HardwareVendor](../modules/Conch%3A%3ARoute%3A%3AHardwareVendor#routes) -### `* /organization` +## `* /organization` See ["routes" in Conch::Route::Organization](../modules/Conch%3A%3ARoute%3A%3AOrganization#routes) -### `* /relay` +## `* /relay` See ["routes" in Conch::Route::Relay](../modules/Conch%3A%3ARoute%3A%3ARelay#routes) -### `* /schema` +## `* /schema` See ["routes" in Conch::Route::Schema](../modules/Conch%3A%3ARoute%3A%3ASchema#routes) -### `* /user` +## `* /user` See ["routes" in Conch::Route::User](../modules/Conch%3A%3ARoute%3A%3AUser#routes) -### `* /validation`, `* /validation_plan`, `* /validation_state` +## `* /validation`, `* /validation_plan`, `* /validation_state` See ["routes" in Conch::Route::Validation](../modules/Conch%3A%3ARoute%3A%3AValidation#routes) -### `* /workspace` +## `* /workspace` See ["routes" in Conch::Route::Workspace](../modules/Conch%3A%3ARoute%3A%3AWorkspace#routes) diff --git a/docs/modules/Conch::Route::Build.md b/docs/modules/Conch::Route::Build.md index fa1559fed..fd78e7916 100644 --- a/docs/modules/Conch::Route::Build.md +++ b/docs/modules/Conch::Route::Build.md @@ -8,9 +8,11 @@ Conch::Route::Build Sets up the routes for /build. +# ROUTE ENDPOINTS + All routes require authentication. -### `GET /build` +## `GET /build` Supports the following optional query parameters: @@ -20,13 +22,13 @@ Supports the following optional query parameters: - Response: response.yaml#/Builds -### `POST /build` +## `POST /build` - Requires system admin authorization - Request: request.yaml#/BuildCreate - Response: Redirect to the build -### `GET /build/:build_id_or_name` +## `GET /build/:build_id_or_name` Supports the following optional query parameters: @@ -37,18 +39,18 @@ Supports the following optional query parameters: - Requires system admin authorization or the read-only role on the build - Response: response.yaml#/Build -### `POST /build/:build_id_or_name` +## `POST /build/:build_id_or_name` - Requires system admin authorization or the admin role on the build - Request: request.yaml#/BuildUpdate - Response: Redirect to the build -### `GET /build/:build_id_or_name/user` +## `GET /build/:build_id_or_name/user` - Requires system admin authorization or the admin role on the build - Response: response.yaml#/BuildUsers -### `POST /build/:build_id_or_name/user?send_mail=<1|0`> +## `POST /build/:build_id_or_name/user?send_mail=<1|0`> Takes one optional query parameter `send_mail=<1|0>` (defaults to 1) to send an email to the user. @@ -57,7 +59,7 @@ an email to the user. - Request: request.yaml#/BuildAddUser - Response: `204 NO CONTENT` -### `DELETE /build/:build_id_or_name/user/#target_user_id_or_email?send_mail=<1|0`> +## `DELETE /build/:build_id_or_name/user/#target_user_id_or_email?send_mail=<1|0`> Takes one optional query parameter `send_mail=<1|0>` (defaults to 1) to send an email to the user. @@ -65,12 +67,12 @@ an email to the user. - Requires system admin authorization or the admin role on the build - Response: `204 NO CONTENT` -### `GET /build/:build_id_or_name/organization` +## `GET /build/:build_id_or_name/organization` - User requires the admin role - Response: [response.json#/definitions/BuildOrganizations](../json-schema/response.json#/definitions/BuildOrganizations) -### `POST /build/:build_id_or_name/organization?send_mail=<1|0>` +## `POST /build/:build_id_or_name/organization?send_mail=<1|0>` Takes one optional query parameter `send_mail=<1|0>` (defaults to 1) to send an email to the organization members and build admins. @@ -79,7 +81,7 @@ an email to the organization members and build admins. - Request: [request.json#/definitions/BuildAddOrganization](../json-schema/request.json#/definitions/BuildAddOrganization) - Response: `204 NO CONTENT` -### `DELETE /build/:build_id_or_name/organization/:organization_id_or_name?send_mail=<1|0>` +## `DELETE /build/:build_id_or_name/organization/:organization_id_or_name?send_mail=<1|0>` Takes one optional query parameter `send_mail=<1|0>` (defaults to 1) to send an email to the organization members and build admins. @@ -87,7 +89,7 @@ an email to the organization members and build admins. - User requires the admin role - Response: `204 NO CONTENT` -### `GET /build/:build_id_or_name/device` +## `GET /build/:build_id_or_name/device` Accepts the following optional query parameters: @@ -99,12 +101,12 @@ Accepts the following optional query parameters: - Requires system admin authorization or the read-only role on the build - Response: [response.json#/definitions/Devices](../json-schema/response.json#/definitions/Devices), [response.json#/definitions/DeviceIds](../json-schema/response.json#/definitions/DeviceIds) or [response.json#/definitions/DeviceSerials](../json-schema/response.json#/definitions/DeviceSerials) -### `GET /build/:build_id_or_name/device/pxe` +## `GET /build/:build_id_or_name/device/pxe` - Requires system admin authorization or the read-only role on the build - Response: [response.json#/definitions/DevicePXEs](../json-schema/response.json#/definitions/DevicePXEs) -### `POST /build/:build_id_or_name/device` +## `POST /build/:build_id_or_name/device` - Requires system admin authorization, or the read/write role on the build and the read-write role on existing device(s) (via a workspace or build; see @@ -112,23 +114,23 @@ read-write role on existing device(s) (via a workspace or build; see - Request: [request.json#/definitions/BuildCreateDevices](../json-schema/request.json#/definitions/BuildCreateDevices) - Response: `204 NO CONTENT` -### `POST /build/:build_id_or_name/device/:device_id_or_serial_number` +## `POST /build/:build_id_or_name/device/:device_id_or_serial_number` - Requires system admin authorization, or the read/write role on the build and the read-write role on the device (via a workspace or build; see ["routes" in Conch::Route::Device](../modules/Conch%3A%3ARoute%3A%3ADevice#routes)) - Response: `204 NO CONTENT` -### `DELETE /build/:build_id_or_name/device/:device_id_or_serial_number` +## `DELETE /build/:build_id_or_name/device/:device_id_or_serial_number` - Requires system admin authorization, or the read/write role on the build - Response: `204 NO CONTENT` -### `GET /build/:build_id_or_name/rack` +## `GET /build/:build_id_or_name/rack` - Requires system admin authorization or the read-only role on the build - Response: response.yaml#/Racks -### `POST /build/:build_id_or_name/rack/:rack_id_or_name` +## `POST /build/:build_id_or_name/rack/:rack_id_or_name` - Requires system admin authorization, or the read/write role on the build and the read-write role on a workspace or build that contains the rack diff --git a/docs/modules/Conch::Route::Datacenter.md b/docs/modules/Conch::Route::Datacenter.md index eaf793289..0533cf6b6 100644 --- a/docs/modules/Conch::Route::Datacenter.md +++ b/docs/modules/Conch::Route::Datacenter.md @@ -6,43 +6,45 @@ Conch::Route::Datacenter ## routes -Sets up the routes for /dc: +Sets up the routes for /dc. + +# ROUTE ENDPOINTS All routes require authentication. -### `GET /dc` +## `GET /dc` - Requires system admin authorization - Response: [response.json#/definitions/Datacenters](../json-schema/response.json#/definitions/Datacenters) -### `POST /dc` +## `POST /dc` - Requires system admin authorization - Request: [request.json#/definitions/DatacenterCreate](../json-schema/request.json#/definitions/DatacenterCreate) - Response: `201 CREATED` or `204 NO CONTENT`, plus Location header -### `GET /dc/:datacenter_id` +## `GET /dc/:datacenter_id` - Requires system admin authorization - Response: [response.json#/definitions/Datacenter](../json-schema/response.json#/definitions/Datacenter) -### `POST /dc/:datacenter_id` +## `POST /dc/:datacenter_id` - Requires system admin authorization - Request: [request.json#/definitions/DatacenterUpdate](../json-schema/request.json#/definitions/DatacenterUpdate) - Response: Redirect to the updated datacenter -### `DELETE /dc/:datacenter_id` +## `DELETE /dc/:datacenter_id` - Requires system admin authorization - Response: `204 NO CONTENT` -### `GET /dc/:datacenter_id/rooms` +## `GET /dc/:datacenter_id/rooms` - Requires system admin authorization - Response: [response.json#/definitions/DatacenterRoomsDetailed](../json-schema/response.json#/definitions/DatacenterRoomsDetailed) -### `GET /room` +## `GET /room` - Requires system admin authorization - Response: [response.json#/definitions/DatacenterRoomsDetailed](../json-schema/response.json#/definitions/DatacenterRoomsDetailed) diff --git a/docs/modules/Conch::Route::DatacenterRoom.md b/docs/modules/Conch::Route::DatacenterRoom.md index 7ced8697a..dcaf6c44a 100644 --- a/docs/modules/Conch::Route::DatacenterRoom.md +++ b/docs/modules/Conch::Route::DatacenterRoom.md @@ -6,83 +6,85 @@ Conch::Route::DatacenterRoom ## routes -Sets up the routes for /room: +Sets up the routes for /room. All routes require authentication. -### `GET /room` +# ROUTE ENDPOINTS + +## `GET /room` - Requires system admin authorization - Response: [response.json#/definitions/DatacenterRoomsDetailed](../json-schema/response.json#/definitions/DatacenterRoomsDetailed) -### `POST /room` +## `POST /room` - Requires system admin authorization - Request: [request.json#/definitions/DatacenterRoomCreate](../json-schema/request.json#/definitions/DatacenterRoomCreate) - Response: Redirect to the created room -### `GET /room/:datacenter_room_id_or_alias` +## `GET /room/:datacenter_room_id_or_alias` - User requires system admin authorization, or the read-only role on a rack located in the room - Response: [response.json#/definitions/DatacenterRoomDetailed](../json-schema/response.json#/definitions/DatacenterRoomDetailed) -### `POST /room/:datacenter_room_id_or_alias` +## `POST /room/:datacenter_room_id_or_alias` - Requires system admin authorization - Request: [request.json#/definitions/DatacenterRoomUpdate](../json-schema/request.json#/definitions/DatacenterRoomUpdate) - Response: Redirect to the updated room -### `DELETE /room/:datacenter_room_id_or_alias` +## `DELETE /room/:datacenter_room_id_or_alias` - Requires system admin authorization - Response: `204 NO CONTENT` -### `GET /room/:datacenter_room_id_or_alias/rack` +## `GET /room/:datacenter_room_id_or_alias/rack` - User requires system admin authorization, or the read-only role on a rack located in the room (in which case data returned is restricted to those racks) - Response: [response.json#/definitions/Racks](../json-schema/response.json#/definitions/Racks) -### `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name` +## `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name` - User requires the read-only role on the rack - Response: [response.json#/definitions/Rack](../json-schema/response.json#/definitions/Rack) -### `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name` +## `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name` - User requires the read/write role on the rack - Request: [request.json#/definitions/RackUpdate](../json-schema/request.json#/definitions/RackUpdate) - Response: Redirect to the updated rack -### `DELETE /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name` +## `DELETE /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name` - Requires system admin authorization - Response: `204 NO CONTENT` -### `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layouts` +## `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layouts` - User requires the read-only role on the rack - Response: [response.json#/definitions/RackLayouts](../json-schema/response.json#/definitions/RackLayouts) -### `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layouts` +## `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layouts` - User requires the read/write role on the rack - Request: [request.json#/definitions/RackLayouts](../json-schema/request.json#/definitions/RackLayouts) - Response: Redirect to the rack's layouts -### `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment` +## `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment` - User requires the read-only role on the rack - Response: [response.json#/definitions/RackAssignments](../json-schema/response.json#/definitions/RackAssignments) -### `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment` +## `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment` - User requires the read/write role on the rack - Request: [request.json#/definitions/RackAssignmentUpdates](../json-schema/request.json#/definitions/RackAssignmentUpdates) - Response: Redirect to the updated rack assignment -### `DELETE /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment` +## `DELETE /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/assignment` This method requires a request body. @@ -90,7 +92,7 @@ This method requires a request body. - Request: [request.json#/definitions/RackAssignmentDeletes](../json-schema/request.json#/definitions/RackAssignmentDeletes) - Response: `204 NO CONTENT` -### `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/phase?rack_only=<0|1>` +## `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/phase?rack_only=<0|1>` The query parameter `rack_only` (defaults to `0`) specifies whether to update only the rack's phase, or all the rack's devices' phases as well. @@ -99,15 +101,15 @@ only the rack's phase, or all the rack's devices' phases as well. - Request: [request.json#/definitions/RackPhase](../json-schema/request.json#/definitions/RackPhase) - Response: Redirect to the updated rack -### `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` +## `GET /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` See ["`GET /layout/:layout_id`" in Conch::Route::RackLayout](../modules/Conch%3A%3ARoute%3A%3ARackLayout#get-layoutlayout_id). -### `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` +## `POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` See ["`POST /layout/:layout_id`" in Conch::Route::RackLayout](../modules/Conch%3A%3ARoute%3A%3ARackLayout#post-layoutlayout_id). -### `DELETE /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` +## `DELETE /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` See ["`DELETE /layout/:layout_id`" in Conch::Route::RackLayout](../modules/Conch%3A%3ARoute%3A%3ARackLayout#delete-layoutlayout_id). diff --git a/docs/modules/Conch::Route::Device.md b/docs/modules/Conch::Route::Device.md index ea679e131..45746bd6e 100644 --- a/docs/modules/Conch::Route::Device.md +++ b/docs/modules/Conch::Route::Device.md @@ -6,7 +6,9 @@ Conch::Route::Device ## routes -Sets up the routes for /device: +Sets up the routes for /device. + +# ROUTE ENDPOINTS All routes require authentication. @@ -19,12 +21,12 @@ a [role](../modules/Conch%3A%3ADB%3A%3AResult%3A%3AUserWorkspaceRole#role) in th Full (admin-level) access is also granted to a device if a report was sent for that device using a relay that registered with that user's credentials. -### `POST /device/:device_serial_number` +## `POST /device/:device_serial_number` - Request: [device_report.json#/definitions/DeviceReport](../json-schema/device_report.json#/definitions/DeviceReport) - Response: [response.json#/definitions/ValidationStateWithResults](../json-schema/response.json#/definitions/ValidationStateWithResults) -### `GET /device?:key=:value` +## `GET /device?:key=:value` Supports the following query parameters: @@ -39,108 +41,108 @@ below. - Response: [response.json#/definitions/Devices](../json-schema/response.json#/definitions/Devices) -### `GET /device/:device_id_or_serial_number` +## `GET /device/:device_id_or_serial_number` - User requires the read-only role - Response: [response.json#/definitions/DetailedDevice](../json-schema/response.json#/definitions/DetailedDevice) -### `GET /device/:device_id_or_serial_number/pxe` +## `GET /device/:device_id_or_serial_number/pxe` - User requires the read-only role - Response: [response.json#/definitions/DevicePXE](../json-schema/response.json#/definitions/DevicePXE) -### `GET /device/:device_id_or_serial_number/phase` +## `GET /device/:device_id_or_serial_number/phase` - User requires the read-only role - Response: [response.json#/definitions/DevicePhase](../json-schema/response.json#/definitions/DevicePhase) -### `GET /device/:device_id_or_serial_number/sku` +## `GET /device/:device_id_or_serial_number/sku` - User requires the read-only role - Response: [response.json#/definitions/DeviceSku](../json-schema/response.json#/definitions/DeviceSku) -### `POST /device/:device_id_or_serial_number/asset_tag` +## `POST /device/:device_id_or_serial_number/asset_tag` - User requires the read/write role - Request: [request.json#/definitions/DeviceAssetTag](../json-schema/request.json#/definitions/DeviceAssetTag) - Response: Redirect to the updated device -### `POST /device/:device_id_or_serial_number/validated` +## `POST /device/:device_id_or_serial_number/validated` - User requires the read/write role - Request: [request.json#/definitions/Null](../json-schema/request.json#/definitions/Null) - Response: Redirect to the updated device -### `POST /device/:device_id_or_serial_number/phase` +## `POST /device/:device_id_or_serial_number/phase` - User requires the read/write role - Request: [request.json#/definitions/DevicePhase](../json-schema/request.json#/definitions/DevicePhase) - Response: Redirect to the updated device -### `POST /device/:device_id_or_serial_number/links` +## `POST /device/:device_id_or_serial_number/links` - User requires the read/write role - Request: [request.json#/definitions/DeviceLinks](../json-schema/request.json#/definitions/DeviceLinks) - Response: Redirect to the updated device -### `DELETE /device/:device_id_or_serial_number/links` +## `DELETE /device/:device_id_or_serial_number/links` - User requires the read/write role - Response: 204 NO CONTENT -### `POST /device/:device_id_or_serial_number/build` +## `POST /device/:device_id_or_serial_number/build` - User requires the read/write role for the device, as well as the old and new builds - Request: [request.json#/definitions/DeviceBuild](../json-schema/request.json#/definitions/DeviceBuild) - Response: Redirect to the updated device -### `GET /device/:device_id_or_serial_number/location` +## `GET /device/:device_id_or_serial_number/location` - User requires the read-only role - Response: [response.json#/definitions/DeviceLocation](../json-schema/response.json#/definitions/DeviceLocation) -### `POST /device/:device_id_or_serial_number/location` +## `POST /device/:device_id_or_serial_number/location` - User requires the read/write role - Request: [request.json#/definitions/DeviceLocationUpdate](../json-schema/request.json#/definitions/DeviceLocationUpdate) - Response: Redirect to the updated device -### `DELETE /device/:device_id_or_serial_number/location` +## `DELETE /device/:device_id_or_serial_number/location` - User requires the read/write role - Response: `204 NO CONTENT` -### `GET /device/:device_id_or_serial_number/settings` +## `GET /device/:device_id_or_serial_number/settings` - User requires the read-only role - Response: [response.json#/definitions/DeviceSettings](../json-schema/response.json#/definitions/DeviceSettings) -### `POST /device/:device_id_or_serial_number/settings` +## `POST /device/:device_id_or_serial_number/settings` - User requires the read/write role, or admin when overwriting existing settings that do not start with `tag.`. - Request: [request.json#/definitions/DeviceSettings](../json-schema/request.json#/definitions/DeviceSettings) - Response: `204 NO CONTENT` -### `GET /device/:device_id_or_serial_number/settings/:key` +## `GET /device/:device_id_or_serial_number/settings/:key` - User requires the read-only role - Response: [response.json#/definitions/DeviceSetting](../json-schema/response.json#/definitions/DeviceSetting) -### `POST /device/:device_id_or_serial_number/settings/:key` +## `POST /device/:device_id_or_serial_number/settings/:key` - User requires the read/write role, or admin when overwriting existing settings that do not start with `tag.`. - Request: [request.json#/definitions/DeviceSettings](../json-schema/request.json#/definitions/DeviceSettings) - Response: `204 NO CONTENT` -### `DELETE /device/:device_id_or_serial_number/settings/:key` +## `DELETE /device/:device_id_or_serial_number/settings/:key` - User requires the read/write role for settings that start with `tag.`, and admin otherwise. - Response: `204 NO CONTENT` -### `POST /device/:device_id_or_serial_number/validation/:validation_id` +## `POST /device/:device_id_or_serial_number/validation/:validation_id` Does not store validation results. @@ -148,7 +150,7 @@ Does not store validation results. - Request: [device_report.json#/definitions/DeviceReport](../json-schema/device_report.json#/definitions/DeviceReport) - Response: [response.json#/definitions/ValidationResults](../json-schema/response.json#/definitions/ValidationResults) -### `POST /device/:device_id_or_serial_number/validation_plan/:validation_plan_id` +## `POST /device/:device_id_or_serial_number/validation_plan/:validation_plan_id` Does not store validation results. @@ -156,7 +158,7 @@ Does not store validation results. - Request: [device_report.json#/definitions/DeviceReport](../json-schema/device_report.json#/definitions/DeviceReport) - Response: [response.json#/definitions/ValidationResults](../json-schema/response.json#/definitions/ValidationResults) -### `GET /device/:device_id_or_serial_number/validation_state?status=&status=...` +## `GET /device/:device_id_or_serial_number/validation_state?status=&status=...` Accepts the query parameter `status`, indicating the desired status(es) to search for (one of `pass`, `fail`, `error`). Can be used more than once. @@ -164,17 +166,17 @@ to search for (one of `pass`, `fail`, `error`). Can be used more than once. - User requires the read-only role - Response: [response.json#/definitions/ValidationStatesWithResults](../json-schema/response.json#/definitions/ValidationStatesWithResults) -### `GET /device/:device_id_or_serial_number/interface` +## `GET /device/:device_id_or_serial_number/interface` - User requires the read-only role - Response: [response.json#/definitions/DeviceNics](../json-schema/response.json#/definitions/DeviceNics) -### `GET /device/:device_id_or_serial_number/interface/:interface_name` +## `GET /device/:device_id_or_serial_number/interface/:interface_name` - User requires the read-only role - Response: [response.json#/definitions/DeviceNic](../json-schema/response.json#/definitions/DeviceNic) -### `GET /device/:device_id_or_serial_number/interface/:interface_name/:field` +## `GET /device/:device_id_or_serial_number/interface/:interface_name/:field` - User requires the read-only role - Response: [response.json#/definitions/DeviceNicField](../json-schema/response.json#/definitions/DeviceNicField) diff --git a/docs/modules/Conch::Route::DeviceReport.md b/docs/modules/Conch::Route::DeviceReport.md index 46d90d683..473dea9dd 100644 --- a/docs/modules/Conch::Route::DeviceReport.md +++ b/docs/modules/Conch::Route::DeviceReport.md @@ -6,16 +6,18 @@ Conch::Route::DeviceReport ## routes -Sets up the routes for /device\_report: +Sets up the routes for /device\_report. + +# ROUTE ENDPOINTS All routes require authentication. -### `POST /device_report` +## `POST /device_report` - Request: [device_report.json#/definitions/DeviceReport](../json-schema/device_report.json#/definitions/DeviceReport) - Response: [response.json#/definitions/ReportValidationResults](../json-schema/response.json#/definitions/ReportValidationResults) -### `GET /device_report/:device_report_id` +## `GET /device_report/:device_report_id` - User requires the read-only role, as described in ["routes" in Conch::Route::Device](../modules/Conch%3A%3ARoute%3A%3ADevice#routes). - Response: [response.json#/definitions/DeviceReportRow](../json-schema/response.json#/definitions/DeviceReportRow) diff --git a/docs/modules/Conch::Route::HardwareProduct.md b/docs/modules/Conch::Route::HardwareProduct.md index f20b2781b..2496f5337 100644 --- a/docs/modules/Conch::Route::HardwareProduct.md +++ b/docs/modules/Conch::Route::HardwareProduct.md @@ -6,27 +6,29 @@ Conch::Route::HardwareProduct ## routes -Sets up the routes for /hardware\_product: +Sets up the routes for /hardware\_product. + +# ROUTE ENDPOINTS All routes require authentication. -### `GET /hardware_product` +## `GET /hardware_product` - Response: [response.json#/definitions/HardwareProducts](../json-schema/response.json#/definitions/HardwareProducts) -### `POST /hardware_product` +## `POST /hardware_product` - Requires system admin authorization - Request: [request.json#/definitions/HardwareProductCreate](../json-schema/request.json#/definitions/HardwareProductCreate) - Response: Redirect to the created hardware product -### `GET /hardware_product/:hardware_product_id_or_other` +## `GET /hardware_product/:hardware_product_id_or_other` Identifiers accepted: `id`, `sku`, `name` and `alias`. - Response: [response.json#/definitions/HardwareProduct](../json-schema/response.json#/definitions/HardwareProduct) -### `POST /hardware_product/:hardware_product_id_or_other` +## `POST /hardware_product/:hardware_product_id_or_other` Identifiers accepted: `id`, `sku`, `name` and `alias`. @@ -34,7 +36,7 @@ Identifiers accepted: `id`, `sku`, `name` and `alias`. - Request: [request.json#/definitions/HardwareProductUpdate](../json-schema/request.json#/definitions/HardwareProductUpdate) - Response: Redirect to the updated hardware product -### `DELETE /hardware_product/:hardware_product_id_or_other` +## `DELETE /hardware_product/:hardware_product_id_or_other` Identifiers accepted: `id`, `sku`, `name` and `alias`. diff --git a/docs/modules/Conch::Route::HardwareVendor.md b/docs/modules/Conch::Route::HardwareVendor.md index baf156920..4d763bd99 100644 --- a/docs/modules/Conch::Route::HardwareVendor.md +++ b/docs/modules/Conch::Route::HardwareVendor.md @@ -6,24 +6,26 @@ Conch::Route::HardwareVendor ## routes -Sets up the routes for /hardware\_vendor: +Sets up the routes for /hardware\_vendor. + +# ROUTE ENDPOINTS All routes require authentication. -### `GET /hardware_vendor` +## `GET /hardware_vendor` - Response: [response.json#/definitions/HardwareVendors](../json-schema/response.json#/definitions/HardwareVendors) -### `GET /hardware_vendor/:hardware_vendor_id_or_name` +## `GET /hardware_vendor/:hardware_vendor_id_or_name` - Response: [response.json#/definitions/HardwareVendor](../json-schema/response.json#/definitions/HardwareVendor) -### `DELETE /hardware_vendor/:hardware_vendor_id_or_name` +## `DELETE /hardware_vendor/:hardware_vendor_id_or_name` - Requires system admin authorization - Response: `204 NO CONTENT` -### `POST /hardware_vendor/:hardware_vendor_name` +## `POST /hardware_vendor/:hardware_vendor_name` - Requires system admin authorization - Request: [request.json#/definitions/Null](../json-schema/request.json#/definitions/Null) diff --git a/docs/modules/Conch::Route::Organization.md b/docs/modules/Conch::Route::Organization.md index 6c0f2b5d2..50b0e85ad 100644 --- a/docs/modules/Conch::Route::Organization.md +++ b/docs/modules/Conch::Route::Organization.md @@ -8,35 +8,37 @@ Conch::Route::Organization Sets up the routes for /organization. +# ROUTE ENDPOINTS + All routes require authentication. -### `GET /organization` +## `GET /organization` - Response: [response.json#/definitions/Organizations](../json-schema/response.json#/definitions/Organizations) -### `POST /organization` +## `POST /organization` - Requires system admin authorization - Request: [request.json#/definitions/OrganizationCreate](../json-schema/request.json#/definitions/OrganizationCreate) - Response: Redirect to the organization -### `GET /organization/:organization_id_or_name` +## `GET /organization/:organization_id_or_name` - Requires system admin authorization or the admin role on the organization - Response: [response.json#/definitions/Organization](../json-schema/response.json#/definitions/Organization) -### `POST /organization/:organization_id_or_name` +## `POST /organization/:organization_id_or_name` - Requires system admin authorization or the admin role on the organization - Request: request.yaml#/OrganizationUpdate - Response: Redirect to the organization -### `DELETE /organization/:organization_id_or_name` +## `DELETE /organization/:organization_id_or_name` - Requires system admin authorization - Response: `204 NO CONTENT` -### `POST /organization/:organization_id_or_name/user?send_mail=<1|0`> +## `POST /organization/:organization_id_or_name/user?send_mail=<1|0`> Takes one optional query parameter `send_mail=<1|0>` (defaults to 1) to send an email to the user. @@ -45,7 +47,7 @@ an email to the user. - Request: [request.json#/definitions/OrganizationAddUser](../json-schema/request.json#/definitions/OrganizationAddUser) - Response: `204 NO CONTENT` -### `DELETE /organization/:organization_id_or_name/user/#target_user_id_or_email?send_mail=<1|0`> +## `DELETE /organization/:organization_id_or_name/user/#target_user_id_or_email?send_mail=<1|0`> Takes one optional query parameter `send_mail=<1|0>` (defaults to 1) to send an email to the user. diff --git a/docs/modules/Conch::Route::Rack.md b/docs/modules/Conch::Route::Rack.md index ce0b11e5b..46dc5d027 100644 --- a/docs/modules/Conch::Route::Rack.md +++ b/docs/modules/Conch::Route::Rack.md @@ -6,63 +6,65 @@ Conch::Route::Rack ## routes -Sets up the routes for /rack: +Sets up the routes for /rack. ## one\_rack\_routes Sets up the routes for working with just one rack, mounted under a provided route prefix. +# ROUTE ENDPOINTS + All routes require authentication. Take note: All routes that reference a specific rack (prefix `/rack/:rack_id`) are also available under `/rack/:rack_id_or_long_name` as well as `/room/datacenter_room_id_or_alias/rack/:rack_id_or_name`. -### `POST /rack` +## `POST /rack` - Requires system admin authorization - Request: [request.json#/definitions/RackCreate](../json-schema/request.json#/definitions/RackCreate) - Response: Redirect to the created rack -### `GET /rack/:rack_id_or_name` +## `GET /rack/:rack_id_or_name` - User requires the read-only role on the rack - Response: [response.json#/definitions/Rack](../json-schema/response.json#/definitions/Rack) -### `POST /rack/:rack_id_or_name` +## `POST /rack/:rack_id_or_name` - User requires the read/write role on the rack - Request: [request.json#/definitions/RackUpdate](../json-schema/request.json#/definitions/RackUpdate) - Response: Redirect to the updated rack -### `DELETE /rack/:rack_id_or_name` +## `DELETE /rack/:rack_id_or_name` - Requires system admin authorization - Response: `204 NO CONTENT` -### `GET /rack/:rack_id_or_name/layout` +## `GET /rack/:rack_id_or_name/layout` - User requires the read-only role on the rack - Response: [response.json#/definitions/RackLayouts](../json-schema/response.json#/definitions/RackLayouts) -### `POST /rack/:rack_id_or_name/layout` +## `POST /rack/:rack_id_or_name/layout` - User requires the read/write role on the rack - Request: [request.json#/definitions/RackLayouts](../json-schema/request.json#/definitions/RackLayouts) - Response: Redirect to the rack's layouts -### `GET /rack/:rack_id_or_name/assignment` +## `GET /rack/:rack_id_or_name/assignment` - User requires the read-only role on the rack - Response: [response.json#/definitions/RackAssignments](../json-schema/response.json#/definitions/RackAssignments) -### `POST /rack/:rack_id_or_name/assignment` +## `POST /rack/:rack_id_or_name/assignment` - User requires the read/write role on the rack - Request: [request.json#/definitions/RackAssignmentUpdates](../json-schema/request.json#/definitions/RackAssignmentUpdates) - Response: Redirect to the updated rack assignment -### `DELETE /rack/:rack_id_or_name/assignment` +## `DELETE /rack/:rack_id_or_name/assignment` This method requires a request body. @@ -70,7 +72,7 @@ This method requires a request body. - Request: [request.json#/definitions/RackAssignmentDeletes](../json-schema/request.json#/definitions/RackAssignmentDeletes) - Response: `204 NO CONTENT` -### `POST /rack/:rack_id_or_name/phase?rack_only=<0|1>` +## `POST /rack/:rack_id_or_name/phase?rack_only=<0|1>` The query parameter `rack_only` (defaults to `0`) specifies whether to update only the rack's phase, or all the rack's devices' phases as well. @@ -79,15 +81,15 @@ only the rack's phase, or all the rack's devices' phases as well. - Request: [request.json#/definitions/RackPhase](../json-schema/request.json#/definitions/RackPhase) - Response: Redirect to the updated rack -### `GET /rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` +## `GET /rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` See ["`GET /layout/:layout_id`" in Conch::Route::RackLayout](../modules/Conch%3A%3ARoute%3A%3ARackLayout#get-layoutlayout_id). -### `POST /rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` +## `POST /rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` See ["`POST /layout/:layout_id`" in Conch::Route::RackLayout](../modules/Conch%3A%3ARoute%3A%3ARackLayout#post-layoutlayout_id). -### `DELETE /rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` +## `DELETE /rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start` See ["`DELETE /layout/:layout_id`" in Conch::Route::RackLayout](../modules/Conch%3A%3ARoute%3A%3ARackLayout#delete-layoutlayout_id). diff --git a/docs/modules/Conch::Route::RackLayout.md b/docs/modules/Conch::Route::RackLayout.md index 9e8937454..437ad0c00 100644 --- a/docs/modules/Conch::Route::RackLayout.md +++ b/docs/modules/Conch::Route::RackLayout.md @@ -6,37 +6,44 @@ Conch::Route::RackLayout ## routes -Sets up the routes for /layout: +Sets up the routes for /layout. ## one\_layout\_routes Sets up the routes for working with just one layout, mounted under a provided route prefix. +# ROUTE ENDPOINTS + All routes require authentication. -### `GET /layout` +Take note: All routes that reference a specific rack layout (prefix `/layout/:layout_id`) are +also available under `/rack/:rack_id_or_long_name/layout/:layout_id_or_rack_unit_start` as +well as +`/room/datacenter_room_id_or_alias/rack/:rack_id_or_name/layout/:layout_id_or_rack_unit_start`. + +## `GET /layout` - Requires system admin authorization - Response: [response.json#/definitions/RackLayouts](../json-schema/response.json#/definitions/RackLayouts) -### `POST /layout` +## `POST /layout` - Requires system admin authorization - Request: [request.json#/definitions/RackLayoutCreate](../json-schema/request.json#/definitions/RackLayoutCreate) - Response: Redirect to the created rack layout -### `GET /layout/:layout_id` +## `GET /layout/:layout_id` - Requires system admin authorization - Response: [response.json#/definitions/RackLayout](../json-schema/response.json#/definitions/RackLayout) -### `POST /layout/:layout_id` +## `POST /layout/:layout_id` - Requires system admin authorization - Request: [request.json#/definitions/RackLayoutUpdate](../json-schema/request.json#/definitions/RackLayoutUpdate) - Response: Redirect to the update rack layout -### `DELETE /layout/:layout_id` +## `DELETE /layout/:layout_id` - Requires system admin authorization - Response: `204 NO CONTENT` diff --git a/docs/modules/Conch::Route::RackRole.md b/docs/modules/Conch::Route::RackRole.md index a21a852fe..0da2c50fc 100644 --- a/docs/modules/Conch::Route::RackRole.md +++ b/docs/modules/Conch::Route::RackRole.md @@ -6,31 +6,33 @@ Conch::Route::RackRole ## routes -Sets up the routes for /rack\_role: +Sets up the routes for /rack\_role. + +# ROUTE ENDPOINTS All routes require authentication. -### `GET /rack_role` +## `GET /rack_role` - Response: [response.json#/definitions/RackRoles](../json-schema/response.json#/definitions/RackRoles) -### `POST /rack_role` +## `POST /rack_role` - Requires system admin authorization - Request: [request.json#/definitions/RackRoleCreate](../json-schema/request.json#/definitions/RackRoleCreate) - Response: Redirect to the created rack role -### `GET /rack_role/:rack_role_id_or_name` +## `GET /rack_role/:rack_role_id_or_name` - Response: [response.json#/definitions/RackRole](../json-schema/response.json#/definitions/RackRole) -### `POST /rack_role/:rack_role_id_or_name` +## `POST /rack_role/:rack_role_id_or_name` - Requires system admin authorization - Request: [request.json#/definitions/RackRoleUpdate](../json-schema/request.json#/definitions/RackRoleUpdate) - Response: Redirect to the updated rack role -### `DELETE /rack_role/:rack_role_id_or_name` +## `DELETE /rack_role/:rack_role_id_or_name` - Requires system admin authorization - Response: `204 NO CONTENT` diff --git a/docs/modules/Conch::Route::Relay.md b/docs/modules/Conch::Route::Relay.md index 81178da61..cbfe70e0a 100644 --- a/docs/modules/Conch::Route::Relay.md +++ b/docs/modules/Conch::Route::Relay.md @@ -6,21 +6,23 @@ Conch::Route::Relay ## routes -Sets up the routes for /relay: +Sets up the routes for /relay. + +# ROUTE ENDPOINTS All routes require authentication. -### `POST /relay/:relay_serial_number/register` +## `POST /relay/:relay_serial_number/register` - Request: [request.json#/definitions/RegisterRelay](../json-schema/request.json#/definitions/RegisterRelay) - Response: `201 CREATED` or `204 NO CONTENT`, plus Location header -### `GET /relay` +## `GET /relay` - Requires system admin authorization - Response: [response.json#/definitions/Relays](../json-schema/response.json#/definitions/Relays) -### `GET /relay/:relay_id_or_serial_number` +## `GET /relay/:relay_id_or_serial_number` - Requires system admin authorization, or the user to have previously registered the relay. - Response: [response.json#/definitions/Relay](../json-schema/response.json#/definitions/Relay) diff --git a/docs/modules/Conch::Route::Schema.md b/docs/modules/Conch::Route::Schema.md index c61e0d804..97b950889 100644 --- a/docs/modules/Conch::Route::Schema.md +++ b/docs/modules/Conch::Route::Schema.md @@ -8,11 +8,13 @@ Conch::Route::Schema Sets up the routes for /schema. -### `GET /schema/query_params/:schema_name` +# ROUTE ENDPOINTS -### `GET /schema/request/:schema_name` +## `GET /schema/query_params/:schema_name` -### `GET /schema/response/:schema_name` +## `GET /schema/request/:schema_name` + +## `GET /schema/response/:schema_name` Returns the schema specified by type and name. diff --git a/docs/modules/Conch::Route::User.md b/docs/modules/Conch::Route::User.md index a697634c4..3cecab2c7 100644 --- a/docs/modules/Conch::Route::User.md +++ b/docs/modules/Conch::Route::User.md @@ -6,15 +6,17 @@ Conch::Route::User ## routes -Sets up the routes for /user: +Sets up the routes for /user. + +# ROUTE ENDPOINTS All routes require authentication. -### `GET /user/me` +## `GET /user/me` - Response: [response.json#/definitions/UserDetailed](../json-schema/response.json#/definitions/UserDetailed) -### `POST /user/:target_user_id_or_email?send_mail=<1|0>` +## `POST /user/:target_user_id_or_email?send_mail=<1|0>` Optionally take the query parameter `send_mail` (defaults to `1`) to send an email telling the user their account was updated @@ -24,7 +26,7 @@ an email telling the user their account was updated - Error response on duplicate user: [response.json#/definitions/UserError](../json-schema/response.json#/definitions/UserError) (only if the calling user is a system admin) -### `POST /user/me/revoke?send_mail=<1|0>&login_only=<0|1>&api_only=<0|1>` +## `POST /user/me/revoke?send_mail=<1|0>&login_only=<0|1>&api_only=<0|1>` Optionally accepts the following query parameters: @@ -38,7 +40,7 @@ By default it will revoke both login/session and API tokens. - Request: [request.json#/definitions/UserSettings](../json-schema/request.json#/definitions/UserSettings) - Response: `204 NO CONTENT` -### `POST /user/me/password?clear_tokens=` +## `POST /user/me/password?clear_tokens=` Optionally takes a query parameter `clear_tokens`, to also revoke the session tokens for the user, forcing the user to log in again. Possible options are: @@ -53,51 +55,51 @@ otherwise, the user is logged out. - Request: [request.json#/definitions/UserSettings](../json-schema/request.json#/definitions/UserSettings) - Response: `204 NO CONTENT` -### `GET /user/me/settings` +## `GET /user/me/settings` - Response: [response.json#/definitions/UserSettings](../json-schema/response.json#/definitions/UserSettings) -### `POST /user/me/settings` +## `POST /user/me/settings` - Request: [request.json#/definitions/UserSettings](../json-schema/request.json#/definitions/UserSettings) - Response: `204 NO CONTENT` -### `GET /user/me/settings/:key` +## `GET /user/me/settings/:key` - Response: [response.json#/definitions/UserSetting](../json-schema/response.json#/definitions/UserSetting) -### `POST /user/me/settings/:key` +## `POST /user/me/settings/:key` - Request: [request.json#/definitions/UserSetting](../json-schema/request.json#/definitions/UserSetting) - Response: `204 NO CONTENT` -### `DELETE /user/me/settings/:key` +## `DELETE /user/me/settings/:key` - Response: `204 NO CONTENT` -### `GET /user/me/token` +## `GET /user/me/token` - Response: [response.json#/definitions/UserTokens](../json-schema/response.json#/definitions/UserTokens) -### `POST /user/me/token` +## `POST /user/me/token` - Request: [request.json#/definitions/NewUserToken](../json-schema/request.json#/definitions/NewUserToken) - Response: [response.json#/definitions/NewUserToken](../json-schema/response.json#/definitions/NewUserToken) -### `GET /user/me/token/:token_name` +## `GET /user/me/token/:token_name` - Response: [response.json#/definitions/UserToken](../json-schema/response.json#/definitions/UserToken) -### `DELETE /user/me/token/:token_name` +## `DELETE /user/me/token/:token_name` - Response: `204 NO CONTENT` -### `GET /user/:target_user_id_or_email` +## `GET /user/:target_user_id_or_email` - Requires system admin authorization (when updating a different account than one's own) - Response: [response.json#/definitions/UserDetailed](../json-schema/response.json#/definitions/UserDetailed) -### `POST /user/:target_user_id_or_email?send_mail=<1|0>` +## `POST /user/:target_user_id_or_email?send_mail=<1|0>` Optionally take the query parameter `send_mail` (defaults to `1`) to send an email telling the user their account was updated @@ -108,7 +110,7 @@ an email telling the user their account was updated - Error response on duplicate user: [response.json#/definitions/UserError](../json-schema/response.json#/definitions/UserError) (only if the calling user is a system admin) -### `DELETE /user/:target_user_id_or_email?clear_tokens=<1|0>` +## `DELETE /user/:target_user_id_or_email?clear_tokens=<1|0>` When a user is deleted, all role entries (workspace, build, organization) are removed and are unrecoverable. @@ -119,7 +121,7 @@ revoke all session tokens for the user forcing all tools to log in again. - Requires system admin authorization - Response: `204 NO CONTENT` -### `POST /user/:target_user_id_or_email/revoke?login_only=<0|1>&api_only=<0|1>` +## `POST /user/:target_user_id_or_email/revoke?login_only=<0|1>&api_only=<0|1>` Optionally accepts the following query parameters: @@ -132,7 +134,7 @@ By default it will revoke both login/session and API tokens. If both - Requires system admin authorization - Response: `204 NO CONTENT` -### `DELETE /user/:target_user_id_or_email/password?clear_tokens=&send_mail=<1|0>` +## `DELETE /user/:target_user_id_or_email/password?clear_tokens=&send_mail=<1|0>` Optionally accepts the following query parameters: @@ -145,12 +147,12 @@ Optionally accepts the following query parameters: - Requires system admin authorization - Response: `204 NO CONTENT` -### `GET /user` +## `GET /user` - Requires system admin authorization - Response: [response.json#/definitions/UsersDetailed](../json-schema/response.json#/definitions/UsersDetailed) -### `POST /user?send_mail=<1|0>` +## `POST /user?send_mail=<1|0>` Optionally takes a query parameter, `send_mail` (defaults to `1`) to send an email to the user with the new password. @@ -160,17 +162,17 @@ email to the user with the new password. - Success Response: [response.json#/definitions/User](../json-schema/response.json#/definitions/User) - Error response on duplicate user: [response.json#/definitions/UserError](../json-schema/response.json#/definitions/UserError) -### `GET /user/:target_user_id_or_email/token` +## `GET /user/:target_user_id_or_email/token` - Requires system admin authorization - Response: [response.json#/definitions/UserTokens](../json-schema/response.json#/definitions/UserTokens) -### `GET /user/:target_user_id_or_email/token/:token_name` +## `GET /user/:target_user_id_or_email/token/:token_name` - Requires system admin authorization - Response: [response.json#/definitions/UserTokens](../json-schema/response.json#/definitions/UserTokens) -### `DELETE /user/:target_user_id_or_email/token/:token_name` +## `DELETE /user/:target_user_id_or_email/token/:token_name` - Requires system admin authorization - Success Response: `204 NO CONTENT` diff --git a/docs/modules/Conch::Route::Validation.md b/docs/modules/Conch::Route::Validation.md index 9b24e6673..c985f5a7b 100644 --- a/docs/modules/Conch::Route::Validation.md +++ b/docs/modules/Conch::Route::Validation.md @@ -6,31 +6,33 @@ Conch::Route::Validation ## routes -Sets up the routes for /validation, /validation\_plan and /validation\_state: +Sets up the routes for /validation, /validation\_plan and /validation\_state. + +# ROUTE ENDPOINTS All routes require authentication. -### `GET /validation` +## `GET /validation` - Response: [response.json#/definitions/Validations](../json-schema/response.json#/definitions/Validations) -### `GET /validation/:validation_id_or_name` +## `GET /validation/:validation_id_or_name` - Response: [response.json#/definitions/Validation](../json-schema/response.json#/definitions/Validation) -### `GET /validation_plan` +## `GET /validation_plan` - Response: [response.json#/definitions/ValidationPlans](../json-schema/response.json#/definitions/ValidationPlans) -### `GET /validation_plan/:validation_plan_id_or_name` +## `GET /validation_plan/:validation_plan_id_or_name` - Response: [response.json#/definitions/ValidationPlan](../json-schema/response.json#/definitions/ValidationPlan) -### `GET /validation_plan/:validation_plan_id_or_name/validation` +## `GET /validation_plan/:validation_plan_id_or_name/validation` - Response: [response.json#/definitions/Validations](../json-schema/response.json#/definitions/Validations) -### `GET /validation_state/:validation_state_id` +## `GET /validation_state/:validation_state_id` - Response: [response.json#/definitions/ValidationStateWithResults](../json-schema/response.json#/definitions/ValidationStateWithResults) diff --git a/docs/modules/Conch::Route::Workspace.md b/docs/modules/Conch::Route::Workspace.md index f59e19983..d22884e4b 100644 --- a/docs/modules/Conch::Route::Workspace.md +++ b/docs/modules/Conch::Route::Workspace.md @@ -11,27 +11,29 @@ Sets up the routes for /workspace. Note that in all routes using `:workspace_id_or_name`, the stash for `workspace_id` will be populated, as well as `workspace_name` if the identifier was not a UUID. +# ROUTE ENDPOINTS + All routes require authentication. Users will require access to the workspace (or one of its ancestors) at a minimum [role](../modules/Conch%3A%3ADB%3A%3AResult%3A%3AUserWorkspaceRole#role), as indicated. -### `GET /workspace` +## `GET /workspace` - User requires the read-only role - Response: [response.json#/definitions/WorkspacesAndRoles](../json-schema/response.json#/definitions/WorkspacesAndRoles) -### `GET /workspace/:workspace_id_or_name` +## `GET /workspace/:workspace_id_or_name` - User requires the read-only role - Response: [response.json#/definitions/WorkspaceAndRole](../json-schema/response.json#/definitions/WorkspaceAndRole) -### `GET /workspace/:workspace_id_or_name/child` +## `GET /workspace/:workspace_id_or_name/child` - User requires the read-only role - Response: [response.json#/definitions/WorkspacesAndRoles](../json-schema/response.json#/definitions/WorkspacesAndRoles) -### `POST /workspace/:workspace_id_or_name/child?send_mail=<1|0>` +## `POST /workspace/:workspace_id_or_name/child?send_mail=<1|0>` Takes one optional query parameter `send_mail=<1|0>` (defaults to `1`) to send an email to the parent workspace admins. @@ -40,7 +42,7 @@ an email to the parent workspace admins. - Request: [request.json#/definitions/WorkspaceCreate](../json-schema/request.json#/definitions/WorkspaceCreate) - Response: [response.json#/definitions/WorkspaceAndRole](../json-schema/response.json#/definitions/WorkspaceAndRole) -### `GET /workspace/:workspace_id_or_name/device` +## `GET /workspace/:workspace_id_or_name/device` Accepts the following optional query parameters: @@ -52,28 +54,28 @@ Accepts the following optional query parameters: - User requires the read-only role - Response: [response.json#/definitions/Devices](../json-schema/response.json#/definitions/Devices), [response.json#/definitions/DeviceIds](../json-schema/response.json#/definitions/DeviceIds) or [response.json#/definitions/DeviceSerials](../json-schema/response.json#/definitions/DeviceSerials) -### `GET /workspace/:workspace_id_or_name/device/pxe` +## `GET /workspace/:workspace_id_or_name/device/pxe` - User requires the read-only role - Response: [response.json#/definitions/WorkspaceDevicePXEs](../json-schema/response.json#/definitions/WorkspaceDevicePXEs) -### `GET /workspace/:workspace_id_or_name/rack` +## `GET /workspace/:workspace_id_or_name/rack` - User requires the read-only role - Response: [response.json#/definitions/WorkspaceRackSummary](../json-schema/response.json#/definitions/WorkspaceRackSummary) -### `POST /workspace/:workspace_id_or_name/rack` +## `POST /workspace/:workspace_id_or_name/rack` - User requires the admin role - Request: [request.json#/definitions/WorkspaceAddRack](../json-schema/request.json#/definitions/WorkspaceAddRack) - Response: Redirect to the workspace's racks -### `DELETE /workspace/:workspace_id_or_name/rack/:rack_id_or_name` +## `DELETE /workspace/:workspace_id_or_name/rack/:rack_id_or_name` - User requires the admin role - Response: `204 NO CONTENT` -### `GET /workspace/:workspace_id_or_name/relay` +## `GET /workspace/:workspace_id_or_name/relay` Takes one query optional parameter, `?active_minutes=X` to constrain results to those updated with in the last `X` minutes. @@ -81,17 +83,17 @@ those updated with in the last `X` minutes. - User requires the read-only role - Response: [response.json#/definitions/WorkspaceRelays](../json-schema/response.json#/definitions/WorkspaceRelays) -### `GET /workspace/:workspace_id_or_name/relay/:relay_id/device` +## `GET /workspace/:workspace_id_or_name/relay/:relay_id/device` - User requires the read-only role - Response: [response.json#/definitions/Devices](../json-schema/response.json#/definitions/Devices) -### `GET /workspace/:workspace_id_or_name/user` +## `GET /workspace/:workspace_id_or_name/user` - User requires the admin role - Response: [response.json#/definitions/WorkspaceUsers](../json-schema/response.json#/definitions/WorkspaceUsers) -### `POST /workspace/:workspace_id_or_name/user?send_mail=<1|0>` +## `POST /workspace/:workspace_id_or_name/user?send_mail=<1|0>` Takes one optional query parameter `send_mail=<1|0>` (defaults to `1`) to send an email to the user and workspace admins. @@ -100,7 +102,7 @@ an email to the user and workspace admins. - Request: [request.json#/definitions/WorkspaceAddUser](../json-schema/request.json#/definitions/WorkspaceAddUser) - Response: `204 NO CONTENT` -### `DELETE /workspace/:workspace_id_or_name/user/:target_user_id_or_email?send_mail=<1|0>` +## `DELETE /workspace/:workspace_id_or_name/user/:target_user_id_or_email?send_mail=<1|0>` Takes one optional query parameter `send_mail=<1|0>` (defaults to `1`) to send an email to the user and workspace admins. @@ -108,12 +110,12 @@ an email to the user and workspace admins. - User requires the admin role - Response: `204 NO CONTENT` -### `GET /workspace/:workspace_id_or_name/organization` +## `GET /workspace/:workspace_id_or_name/organization` - User requires the admin role - Response: [response.json#/definitions/WorkspaceOrganizations](../json-schema/response.json#/definitions/WorkspaceOrganizations) -### `POST /workspace/:workspace_id_or_name/organization?send_mail=<1|0>` +## `POST /workspace/:workspace_id_or_name/organization?send_mail=<1|0>` Takes one optional query parameter `send_mail=<1|0>` (defaults to 1) to send an email to the organization members and workspace admins. @@ -122,7 +124,7 @@ an email to the organization members and workspace admins. - Request: [request.json#/definitions/WorkspaceAddOrganization](../json-schema/request.json#/definitions/WorkspaceAddOrganization) - Response: `204 NO CONTENT` -### `DELETE /workspace/:workspace_id_or_name/organization/:organization_id_or_name?send_mail=<1|0>` +## `DELETE /workspace/:workspace_id_or_name/organization/:organization_id_or_name?send_mail=<1|0>` Takes one optional query parameter `send_mail=<1|0>` (defaults to 1) to send an email to the organization members and workspace admins. diff --git a/lib/Conch/Route.pm b/lib/Conch/Route.pm index 50aab7689..ea652bf0b 100644 --- a/lib/Conch/Route.pm +++ b/lib/Conch/Route.pm @@ -46,7 +46,16 @@ sub all_routes ( $app, # the Conch app ) { - # provides a route to chain to that first checks the user is a system admin. +=head1 SHORTCUTS + +These are available on the root router. See L. + +=head2 require_system_admin + +Chainable route that aborts with HTTP 403 if the user is not a system admin. + +=cut + $root->add_shortcut(require_system_admin => sub ($r) { $r->any(sub ($c) { return $c->status(401) @@ -132,26 +141,28 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + Unless otherwise specified, all routes require authentication. Full access is granted to system admin users, regardless of workspace, build or other role entries. -Successful (http 2xx code) response structures are as described for each endpoint. +Successful (HTTP 2xx code) response structures are as described for each endpoint. Error responses will use: =over -=item * failure to validate query parameters: http 400, F +=item * failure to validate query parameters: HTTP 400, F -=item * failure to validate request body payload: http 400, F +=item * failure to validate request body payload: HTTP 400, F -=item * all other errors, unless specified: http 4xx, F +=item * all other errors, unless specified: HTTP 4xx, F =back -=head3 C +=head2 C =over 4 @@ -161,7 +172,7 @@ Error responses will use: =back -=head3 C +=head2 C =over 4 @@ -171,7 +182,7 @@ Error responses will use: =back -=head3 C +=head2 C =over 4 @@ -181,7 +192,7 @@ Error responses will use: =back -=head3 C +=head2 C =over 4 @@ -191,9 +202,9 @@ Error responses will use: =back -=head3 C +=head2 C -=head3 C +=head2 C =over 4 @@ -205,7 +216,7 @@ Error responses will use: =back -=head3 C +=head2 C =over 4 @@ -215,47 +226,47 @@ Error responses will use: =back -=head3 C<* /dc>, C<* /room>, C<* /rack_role>, C<* /rack>, C<* /layout> +=head2 C<* /dc>, C<* /room>, C<* /rack_role>, C<* /rack>, C<* /layout> See L -=head3 C<* /device> +=head2 C<* /device> See L -=head3 C<* /device_report> +=head2 C<* /device_report> See L -=head3 C<* /hardware_product> +=head2 C<* /hardware_product> See L -=head3 C<* /hardware_vendor> +=head2 C<* /hardware_vendor> See L -=head3 C<* /organization> +=head2 C<* /organization> See L -=head3 C<* /relay> +=head2 C<* /relay> See L -=head3 C<* /schema> +=head2 C<* /schema> See L -=head3 C<* /user> +=head2 C<* /user> See L -=head3 C<* /validation>, C<* /validation_plan>, C<* /validation_state> +=head2 C<* /validation>, C<* /validation_plan>, C<* /validation_state> See L -=head3 C<* /workspace> +=head2 C<* /workspace> See L diff --git a/lib/Conch/Route/Build.pm b/lib/Conch/Route/Build.pm index 1a3022049..bee781f96 100644 --- a/lib/Conch/Route/Build.pm +++ b/lib/Conch/Route/Build.pm @@ -108,9 +108,11 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +=head2 C Supports the following optional query parameters: @@ -130,7 +132,7 @@ Supports the following optional query parameters: =back -=head3 C +=head2 C =over 4 @@ -142,7 +144,7 @@ Supports the following optional query parameters: =back -=head3 C +=head2 C Supports the following optional query parameters: @@ -164,7 +166,7 @@ Supports the following optional query parameters: =back -=head3 C +=head2 C =over 4 @@ -176,7 +178,7 @@ Supports the following optional query parameters: =back -=head3 C +=head2 C =over 4 @@ -186,7 +188,7 @@ Supports the following optional query parameters: =back -=head3 C> +=head2 C> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to 1) to send an email to the user. @@ -201,7 +203,7 @@ an email to the user. =back -=head3 C> +=head2 C> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to 1) to send an email to the user. @@ -214,7 +216,7 @@ an email to the user. =back -=head3 C +=head2 C =over 4 @@ -224,7 +226,7 @@ an email to the user. =back -=head3 C<< POST /build/:build_id_or_name/organization?send_mail=<1|0> >> +=head2 C<< POST /build/:build_id_or_name/organization?send_mail=<1|0> >> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to 1) to send an email to the organization members and build admins. @@ -239,7 +241,7 @@ an email to the organization members and build admins. =back -=head3 C<< DELETE /build/:build_id_or_name/organization/:organization_id_or_name?send_mail=<1|0> >> +=head2 C<< DELETE /build/:build_id_or_name/organization/:organization_id_or_name?send_mail=<1|0> >> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to 1) to send an email to the organization members and build admins. @@ -252,7 +254,7 @@ an email to the organization members and build admins. =back -=head3 C +=head2 C Accepts the following optional query parameters: @@ -275,7 +277,7 @@ Accepts the following optional query parameters: =back -=head3 C +=head2 C =over 4 @@ -285,7 +287,7 @@ Accepts the following optional query parameters: =back -=head3 C +=head2 C =over 4 @@ -299,7 +301,7 @@ L) =back -=head3 C +=head2 C =over 4 @@ -310,7 +312,7 @@ read-write role on the device (via a workspace or build; see L +=head2 C =over 4 @@ -320,7 +322,7 @@ read-write role on the device (via a workspace or build; see L +=head2 C =over 4 @@ -330,7 +332,7 @@ read-write role on the device (via a workspace or build; see L +=head2 C =over 4 diff --git a/lib/Conch/Route/Datacenter.pm b/lib/Conch/Route/Datacenter.pm index 0a1cf90db..f1709b544 100644 --- a/lib/Conch/Route/Datacenter.pm +++ b/lib/Conch/Route/Datacenter.pm @@ -12,7 +12,7 @@ Conch::Route::Datacenter =head2 routes -Sets up the routes for /dc: +Sets up the routes for /dc. =cut @@ -44,9 +44,11 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +=head2 C =over 4 @@ -56,7 +58,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -68,7 +70,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -78,7 +80,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -90,7 +92,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -100,7 +102,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -110,7 +112,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 diff --git a/lib/Conch/Route/DatacenterRoom.pm b/lib/Conch/Route/DatacenterRoom.pm index 159558004..b343a5bd0 100644 --- a/lib/Conch/Route/DatacenterRoom.pm +++ b/lib/Conch/Route/DatacenterRoom.pm @@ -12,7 +12,7 @@ Conch::Route::DatacenterRoom =head2 routes -Sets up the routes for /room: +Sets up the routes for /room. =cut @@ -72,7 +72,9 @@ __END__ All routes require authentication. -=head3 C +=head1 ROUTE ENDPOINTS + +=head2 C =over 4 @@ -82,7 +84,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -94,7 +96,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -105,7 +107,7 @@ the room =back -=head3 C +=head2 C =over 4 @@ -117,7 +119,7 @@ the room =back -=head3 C +=head2 C =over 4 @@ -127,7 +129,7 @@ the room =back -=head3 C +=head2 C =over 4 @@ -138,7 +140,7 @@ the room (in which case data returned is restricted to those racks) =back -=head3 C +=head2 C =over 4 @@ -148,7 +150,7 @@ the room (in which case data returned is restricted to those racks) =back -=head3 C +=head2 C =over 4 @@ -160,7 +162,7 @@ the room (in which case data returned is restricted to those racks) =back -=head3 C +=head2 C =over 4 @@ -170,7 +172,7 @@ the room (in which case data returned is restricted to those racks) =back -=head3 C +=head2 C =over 4 @@ -180,7 +182,7 @@ the room (in which case data returned is restricted to those racks) =back -=head3 C +=head2 C =over 4 @@ -192,7 +194,7 @@ the room (in which case data returned is restricted to those racks) =back -=head3 C +=head2 C =over 4 @@ -202,7 +204,7 @@ the room (in which case data returned is restricted to those racks) =back -=head3 C +=head2 C =over 4 @@ -214,7 +216,7 @@ the room (in which case data returned is restricted to those racks) =back -=head3 C +=head2 C This method requires a request body. @@ -228,7 +230,7 @@ This method requires a request body. =back -=head3 C<< POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/phase?rack_only=<0|1> >> +=head2 C<< POST /room/:datacenter_room_id_or_alias/rack/:rack_id_or_name/phase?rack_only=<0|1> >> The query parameter C (defaults to C<0>) specifies whether to update only the rack's phase, or all the rack's devices' phases as well. @@ -243,15 +245,15 @@ only the rack's phase, or all the rack's devices' phases as well. =back -=head3 C +=head2 C See L>. -=head3 C +=head2 C See L>. -=head3 C +=head2 C See L>. diff --git a/lib/Conch/Route/Device.pm b/lib/Conch/Route/Device.pm index d404ccabc..1b6f84338 100644 --- a/lib/Conch/Route/Device.pm +++ b/lib/Conch/Route/Device.pm @@ -12,7 +12,7 @@ Conch::Route::Device =head2 routes -Sets up the routes for /device: +Sets up the routes for /device. =cut @@ -121,6 +121,8 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. The user's role (required for most endpoints) is determined by the build the device is @@ -132,7 +134,7 @@ a L in that workspace). Full (admin-level) access is also granted to a device if a report was sent for that device using a relay that registered with that user's credentials. -=head3 C +=head2 C =over 4 @@ -142,7 +144,7 @@ using a relay that registered with that user's credentials. =back -=head3 C +=head2 C Supports the following query parameters: @@ -168,7 +170,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -178,7 +180,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -188,7 +190,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -198,7 +200,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -208,7 +210,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -220,7 +222,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -232,7 +234,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -244,7 +246,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -256,7 +258,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -266,7 +268,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -278,7 +280,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -288,7 +290,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -300,7 +302,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -310,7 +312,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -320,7 +322,7 @@ below. =back -=head3 C +=head2 C =over 4 @@ -333,7 +335,7 @@ settings that do not start with C. =back -=head3 C +=head2 C =over 4 @@ -343,7 +345,7 @@ settings that do not start with C. =back -=head3 C +=head2 C =over 4 @@ -356,7 +358,7 @@ settings that do not start with C. =back -=head3 C +=head2 C =over 4 @@ -367,7 +369,7 @@ otherwise. =back -=head3 C +=head2 C Does not store validation results. @@ -381,7 +383,7 @@ Does not store validation results. =back -=head3 C +=head2 C Does not store validation results. @@ -395,7 +397,7 @@ Does not store validation results. =back -=head3 C<< GET /device/:device_id_or_serial_number/validation_state?status=&status=... >> +=head2 C<< GET /device/:device_id_or_serial_number/validation_state?status=&status=... >> Accepts the query parameter C, indicating the desired status(es) to search for (one of C, C, C). Can be used more than once. @@ -408,7 +410,7 @@ to search for (one of C, C, C). Can be used more than once. =back -=head3 C +=head2 C =over 4 @@ -418,7 +420,7 @@ to search for (one of C, C, C). Can be used more than once. =back -=head3 C +=head2 C =over 4 @@ -428,7 +430,7 @@ to search for (one of C, C, C). Can be used more than once. =back -=head3 C +=head2 C =over 4 diff --git a/lib/Conch/Route/DeviceReport.pm b/lib/Conch/Route/DeviceReport.pm index 3d0ff1aa4..ff25f1f2e 100644 --- a/lib/Conch/Route/DeviceReport.pm +++ b/lib/Conch/Route/DeviceReport.pm @@ -12,7 +12,7 @@ Conch::Route::DeviceReport =head2 routes -Sets up the routes for /device_report: +Sets up the routes for /device_report. =cut @@ -40,9 +40,11 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +=head2 C =over 4 @@ -52,7 +54,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 diff --git a/lib/Conch/Route/HardwareProduct.pm b/lib/Conch/Route/HardwareProduct.pm index 95fcb2418..c188332b7 100644 --- a/lib/Conch/Route/HardwareProduct.pm +++ b/lib/Conch/Route/HardwareProduct.pm @@ -12,7 +12,7 @@ Conch::Route::HardwareProduct =head2 routes -Sets up the routes for /hardware_product: +Sets up the routes for /hardware_product. =cut @@ -49,9 +49,11 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +=head2 C =over 4 @@ -59,7 +61,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -71,7 +73,7 @@ All routes require authentication. =back -=head3 C +=head2 C Identifiers accepted: C, C, C and C. @@ -81,7 +83,7 @@ Identifiers accepted: C, C, C and C. =back -=head3 C +=head2 C Identifiers accepted: C, C, C and C. @@ -95,7 +97,7 @@ Identifiers accepted: C, C, C and C. =back -=head3 C +=head2 C Identifiers accepted: C, C, C and C. diff --git a/lib/Conch/Route/HardwareVendor.pm b/lib/Conch/Route/HardwareVendor.pm index 0ce8f5ae5..3370e062c 100644 --- a/lib/Conch/Route/HardwareVendor.pm +++ b/lib/Conch/Route/HardwareVendor.pm @@ -12,7 +12,7 @@ Conch::Route::HardwareVendor =head2 routes -Sets up the routes for /hardware_vendor: +Sets up the routes for /hardware_vendor. =cut @@ -45,9 +45,11 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +=head2 C =over 4 @@ -55,7 +57,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -63,7 +65,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -73,7 +75,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 diff --git a/lib/Conch/Route/Organization.pm b/lib/Conch/Route/Organization.pm index 9e49d3868..44b80b60f 100644 --- a/lib/Conch/Route/Organization.pm +++ b/lib/Conch/Route/Organization.pm @@ -57,9 +57,11 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +=head2 C =over 4 @@ -67,7 +69,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -79,7 +81,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -89,7 +91,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -101,7 +103,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -111,7 +113,7 @@ All routes require authentication. =back -=head3 C> +=head2 C> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to 1) to send an email to the user. @@ -126,7 +128,7 @@ an email to the user. =back -=head3 C> +=head2 C> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to 1) to send an email to the user. diff --git a/lib/Conch/Route/Rack.pm b/lib/Conch/Route/Rack.pm index 0ff2aaf81..0381ec3e9 100644 --- a/lib/Conch/Route/Rack.pm +++ b/lib/Conch/Route/Rack.pm @@ -12,7 +12,7 @@ Conch::Route::Rack =head2 routes -Sets up the routes for /rack: +Sets up the routes for /rack. =cut @@ -87,13 +87,15 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. Take note: All routes that reference a specific rack (prefix C) are also available under C as well as C. -=head3 C +=head2 C =over 4 @@ -105,7 +107,7 @@ C. =back -=head3 C +=head2 C =over 4 @@ -115,7 +117,7 @@ C. =back -=head3 C +=head2 C =over 4 @@ -127,7 +129,7 @@ C. =back -=head3 C +=head2 C =over 4 @@ -137,7 +139,7 @@ C. =back -=head3 C +=head2 C =over 4 @@ -147,7 +149,7 @@ C. =back -=head3 C +=head2 C =over 4 @@ -159,7 +161,7 @@ C. =back -=head3 C +=head2 C =over 4 @@ -169,7 +171,7 @@ C. =back -=head3 C +=head2 C =over 4 @@ -181,7 +183,7 @@ C. =back -=head3 C +=head2 C This method requires a request body. @@ -195,7 +197,7 @@ This method requires a request body. =back -=head3 C<< POST /rack/:rack_id_or_name/phase?rack_only=<0|1> >> +=head2 C<< POST /rack/:rack_id_or_name/phase?rack_only=<0|1> >> The query parameter C (defaults to C<0>) specifies whether to update only the rack's phase, or all the rack's devices' phases as well. @@ -210,15 +212,15 @@ only the rack's phase, or all the rack's devices' phases as well. =back -=head3 C +=head2 C See L>. -=head3 C +=head2 C See L>. -=head3 C +=head2 C See L>. diff --git a/lib/Conch/Route/RackLayout.pm b/lib/Conch/Route/RackLayout.pm index 3f881036f..bfe39859a 100644 --- a/lib/Conch/Route/RackLayout.pm +++ b/lib/Conch/Route/RackLayout.pm @@ -12,7 +12,7 @@ Conch::Route::RackLayout =head2 routes -Sets up the routes for /layout: +Sets up the routes for /layout. =cut @@ -55,9 +55,16 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +Take note: All routes that reference a specific rack layout (prefix C) are +also available under C as +well as +C. + +=head2 C =over 4 @@ -67,7 +74,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -79,7 +86,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -89,7 +96,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -101,7 +108,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 diff --git a/lib/Conch/Route/RackRole.pm b/lib/Conch/Route/RackRole.pm index dfed454e4..1f76df8e7 100644 --- a/lib/Conch/Route/RackRole.pm +++ b/lib/Conch/Route/RackRole.pm @@ -12,7 +12,7 @@ Conch::Route::RackRole =head2 routes -Sets up the routes for /rack_role: +Sets up the routes for /rack_role. =cut @@ -42,9 +42,11 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +=head2 C =over 4 @@ -52,7 +54,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -64,7 +66,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -72,7 +74,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -84,7 +86,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 diff --git a/lib/Conch/Route/Relay.pm b/lib/Conch/Route/Relay.pm index cd61abc65..836fad8ca 100644 --- a/lib/Conch/Route/Relay.pm +++ b/lib/Conch/Route/Relay.pm @@ -12,7 +12,7 @@ Conch::Route::Relay =head2 routes -Sets up the routes for /relay: +Sets up the routes for /relay. =cut @@ -42,9 +42,11 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +=head2 C =over 4 @@ -54,7 +56,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -64,7 +66,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 diff --git a/lib/Conch/Route/Schema.pm b/lib/Conch/Route/Schema.pm index d4e19730b..7df5836f5 100644 --- a/lib/Conch/Route/Schema.pm +++ b/lib/Conch/Route/Schema.pm @@ -34,11 +34,13 @@ __END__ =pod -=head3 C +=head1 ROUTE ENDPOINTS -=head3 C +=head2 C -=head3 C +=head2 C + +=head2 C Returns the schema specified by type and name. diff --git a/lib/Conch/Route/User.pm b/lib/Conch/Route/User.pm index 3a389586e..faf23a4ac 100644 --- a/lib/Conch/Route/User.pm +++ b/lib/Conch/Route/User.pm @@ -12,7 +12,7 @@ Conch::Route::User =head2 routes -Sets up the routes for /user: +Sets up the routes for /user. =cut @@ -129,9 +129,11 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +=head2 C =over 4 @@ -139,7 +141,7 @@ All routes require authentication. =back -=head3 C<< POST /user/:target_user_id_or_email?send_mail=<1|0> >> +=head2 C<< POST /user/:target_user_id_or_email?send_mail=<1|0> >> Optionally take the query parameter C (defaults to C<1>) to send an email telling the user their account was updated @@ -155,7 +157,7 @@ calling user is a system admin) =back -=head3 C<< POST /user/me/revoke?send_mail=<1|0>&login_only=<0|1>&api_only=<0|1> >> +=head2 C<< POST /user/me/revoke?send_mail=<1|0>&login_only=<0|1>&api_only=<0|1> >> Optionally accepts the following query parameters: @@ -180,7 +182,7 @@ C and C cannot both be C<1>. =back -=head3 C<< POST /user/me/password?clear_tokens= >> +=head2 C<< POST /user/me/password?clear_tokens= >> Optionally takes a query parameter C, to also revoke the session tokens for the user, forcing the user to log in again. Possible options are: @@ -207,7 +209,7 @@ otherwise, the user is logged out. =back -=head3 C +=head2 C =over 4 @@ -215,7 +217,7 @@ otherwise, the user is logged out. =back -=head3 C +=head2 C =over 4 @@ -225,7 +227,7 @@ otherwise, the user is logged out. =back -=head3 C +=head2 C =over 4 @@ -233,7 +235,7 @@ otherwise, the user is logged out. =back -=head3 C +=head2 C =over 4 @@ -243,7 +245,7 @@ otherwise, the user is logged out. =back -=head3 C +=head2 C =over 4 @@ -251,7 +253,7 @@ otherwise, the user is logged out. =back -=head3 C +=head2 C =over 4 @@ -259,7 +261,7 @@ otherwise, the user is logged out. =back -=head3 C +=head2 C =over 4 @@ -269,7 +271,7 @@ otherwise, the user is logged out. =back -=head3 C +=head2 C =over 4 @@ -277,7 +279,7 @@ otherwise, the user is logged out. =back -=head3 C +=head2 C =over 4 @@ -285,7 +287,7 @@ otherwise, the user is logged out. =back -=head3 C +=head2 C =over 4 @@ -295,7 +297,7 @@ otherwise, the user is logged out. =back -=head3 C<< POST /user/:target_user_id_or_email?send_mail=<1|0> >> +=head2 C<< POST /user/:target_user_id_or_email?send_mail=<1|0> >> Optionally take the query parameter C (defaults to C<1>) to send an email telling the user their account was updated @@ -313,7 +315,7 @@ calling user is a system admin) =back -=head3 C<< DELETE /user/:target_user_id_or_email?clear_tokens=<1|0> >> +=head2 C<< DELETE /user/:target_user_id_or_email?clear_tokens=<1|0> >> When a user is deleted, all role entries (workspace, build, organization) are removed and are unrecoverable. @@ -329,7 +331,7 @@ revoke all session tokens for the user forcing all tools to log in again. =back -=head3 C<< POST /user/:target_user_id_or_email/revoke?login_only=<0|1>&api_only=<0|1> >> +=head2 C<< POST /user/:target_user_id_or_email/revoke?login_only=<0|1>&api_only=<0|1> >> Optionally accepts the following query parameters: @@ -352,7 +354,7 @@ C and C cannot both be C<1>. =back -=head3 C<< DELETE /user/:target_user_id_or_email/password?clear_tokens=&send_mail=<1|0> >> +=head2 C<< DELETE /user/:target_user_id_or_email/password?clear_tokens=&send_mail=<1|0> >> Optionally accepts the following query parameters: @@ -382,7 +384,7 @@ Optionally accepts the following query parameters: =back -=head3 C +=head2 C =over 4 @@ -392,7 +394,7 @@ Optionally accepts the following query parameters: =back -=head3 C<< POST /user?send_mail=<1|0> >> +=head2 C<< POST /user?send_mail=<1|0> >> Optionally takes a query parameter, C (defaults to C<1>) to send an email to the user with the new password. @@ -409,7 +411,7 @@ email to the user with the new password. =back -=head3 C +=head2 C =over 4 @@ -419,7 +421,7 @@ email to the user with the new password. =back -=head3 C +=head2 C =over 4 @@ -429,7 +431,7 @@ email to the user with the new password. =back -=head3 C +=head2 C =over 4 diff --git a/lib/Conch/Route/Validation.pm b/lib/Conch/Route/Validation.pm index a8de02bd8..fdc112dba 100644 --- a/lib/Conch/Route/Validation.pm +++ b/lib/Conch/Route/Validation.pm @@ -12,7 +12,7 @@ Conch::Route::Validation =head2 routes -Sets up the routes for /validation, /validation_plan and /validation_state: +Sets up the routes for /validation, /validation_plan and /validation_state. =cut @@ -66,9 +66,11 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. -=head3 C +=head2 C =over 4 @@ -76,7 +78,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -84,7 +86,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -92,7 +94,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -100,7 +102,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 @@ -108,7 +110,7 @@ All routes require authentication. =back -=head3 C +=head2 C =over 4 diff --git a/lib/Conch/Route/Workspace.pm b/lib/Conch/Route/Workspace.pm index 08adca85e..d13ffdc90 100644 --- a/lib/Conch/Route/Workspace.pm +++ b/lib/Conch/Route/Workspace.pm @@ -83,12 +83,14 @@ __END__ =pod +=head1 ROUTE ENDPOINTS + All routes require authentication. Users will require access to the workspace (or one of its ancestors) at a minimum L, as indicated. -=head3 C +=head2 C =over 4 @@ -98,7 +100,7 @@ L, as indicated. =back -=head3 C +=head2 C =over 4 @@ -108,7 +110,7 @@ L, as indicated. =back -=head3 C +=head2 C =over 4 @@ -118,7 +120,7 @@ L, as indicated. =back -=head3 C<< POST /workspace/:workspace_id_or_name/child?send_mail=<1|0> >> +=head2 C<< POST /workspace/:workspace_id_or_name/child?send_mail=<1|0> >> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to C<1>) to send an email to the parent workspace admins. @@ -133,7 +135,7 @@ an email to the parent workspace admins. =back -=head3 C +=head2 C Accepts the following optional query parameters: @@ -157,7 +159,7 @@ Accepts the following optional query parameters: =back -=head3 C +=head2 C =over 4 @@ -167,7 +169,7 @@ Accepts the following optional query parameters: =back -=head3 C +=head2 C =over 4 @@ -177,7 +179,7 @@ Accepts the following optional query parameters: =back -=head3 C +=head2 C =over 4 @@ -189,7 +191,7 @@ Accepts the following optional query parameters: =back -=head3 C +=head2 C =over 4 @@ -199,7 +201,7 @@ Accepts the following optional query parameters: =back -=head3 C +=head2 C Takes one query optional parameter, C to constrain results to those updated with in the last C minutes. @@ -212,7 +214,7 @@ those updated with in the last C minutes. =back -=head3 C +=head2 C =over 4 @@ -222,7 +224,7 @@ those updated with in the last C minutes. =back -=head3 C +=head2 C =over 4 @@ -232,7 +234,7 @@ those updated with in the last C minutes. =back -=head3 C<< POST /workspace/:workspace_id_or_name/user?send_mail=<1|0> >> +=head2 C<< POST /workspace/:workspace_id_or_name/user?send_mail=<1|0> >> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to C<1>) to send an email to the user and workspace admins. @@ -247,7 +249,7 @@ an email to the user and workspace admins. =back -=head3 C<< DELETE /workspace/:workspace_id_or_name/user/:target_user_id_or_email?send_mail=<1|0> >> +=head2 C<< DELETE /workspace/:workspace_id_or_name/user/:target_user_id_or_email?send_mail=<1|0> >> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to C<1>) to send an email to the user and workspace admins. @@ -260,7 +262,7 @@ an email to the user and workspace admins. =back -=head3 C +=head2 C =over 4 @@ -270,7 +272,7 @@ an email to the user and workspace admins. =back -=head3 C<< POST /workspace/:workspace_id_or_name/organization?send_mail=<1|0> >> +=head2 C<< POST /workspace/:workspace_id_or_name/organization?send_mail=<1|0> >> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to 1) to send an email to the organization members and workspace admins. @@ -285,7 +287,7 @@ an email to the organization members and workspace admins. =back -=head3 C<< DELETE /workspace/:workspace_id_or_name/organization/:organization_id_or_name?send_mail=<1|0> >> +=head2 C<< DELETE /workspace/:workspace_id_or_name/organization/:organization_id_or_name?send_mail=<1|0> >> Takes one optional query parameter C<< send_mail=<1|0> >> (defaults to 1) to send an email to the organization members and workspace admins. From 55fccd0655f5334f6c25921ce88aa7748de47b37 Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Mon, 13 Jan 2020 14:24:23 -0800 Subject: [PATCH 06/18] combine some duplicate code for user lookups into a common shortcut --- docs/json-schema/request.json | 24 ++++++++++++++++++++++++ docs/modules/Conch::Route.md | 5 +++++ json-schema/request.yaml | 13 +++++++++++++ lib/Conch/Controller/Build.pm | 11 +---------- lib/Conch/Controller/Organization.pm | 11 +---------- lib/Conch/Controller/User.pm | 2 +- lib/Conch/Controller/WorkspaceUser.pm | 7 +------ lib/Conch/Route.pm | 23 +++++++++++++++++++++-- lib/Conch/Route/Build.pm | 2 +- lib/Conch/Route/Organization.pm | 2 +- lib/Conch/Route/Workspace.pm | 2 +- 11 files changed, 70 insertions(+), 32 deletions(-) diff --git a/docs/json-schema/request.json b/docs/json-schema/request.json index 1fae95d80..a5389f6ce 100644 --- a/docs/json-schema/request.json +++ b/docs/json-schema/request.json @@ -1085,6 +1085,30 @@ }, "type" : "object" }, + "UserIdOrEmail" : { + "additionalProperties" : true, + "oneOf" : [ + { + "required" : [ + "user_id" + ] + }, + { + "required" : [ + "email" + ] + } + ], + "properties" : { + "email" : { + "$ref" : "common.json#/definitions/email_address" + }, + "user_id" : { + "$ref" : "common.json#/definitions/uuid" + } + }, + "type" : "object" + }, "UserPassword" : { "additionalProperties" : false, "properties" : { diff --git a/docs/modules/Conch::Route.md b/docs/modules/Conch::Route.md index d1eb97a09..ea54a5d64 100644 --- a/docs/modules/Conch::Route.md +++ b/docs/modules/Conch::Route.md @@ -20,6 +20,11 @@ These are available on the root router. See ["Shortcuts" in Mojolicious::Guides: Chainable route that aborts with HTTP 403 if the user is not a system admin. +## find\_user\_from\_payload + +Chainable route that looks up the user by `user_id` or `email` in the JSON payload, +aborting with HTTP 410 or HTTP 404 if not found. + # ROUTE ENDPOINTS Unless otherwise specified, all routes require authentication. diff --git a/json-schema/request.yaml b/json-schema/request.yaml index 11e842d55..0e3e0972f 100644 --- a/json-schema/request.yaml +++ b/json-schema/request.yaml @@ -369,6 +369,19 @@ definitions: $ref: common.yaml#/definitions/email_address password: $ref: common.yaml#/definitions/non_empty_string + UserIdOrEmail: + type: object + additionalProperties: true + oneOf: + - required: + - user_id + - required: + - email + properties: + user_id: + $ref: common.yaml#/definitions/uuid + email: + $ref: common.yaml#/definitions/email_address UserPassword: type: object additionalProperties: false diff --git a/lib/Conch/Controller/Build.pm b/lib/Conch/Controller/Build.pm index bbc0ac4aa..3318f19db 100644 --- a/lib/Conch/Controller/Build.pm +++ b/lib/Conch/Controller/Build.pm @@ -278,16 +278,7 @@ sub add_user ($c) { my $input = $c->validate_request('BuildAddUser'); return if not $input; - my $user_rs = $c->db_user_accounts->active; - my $user = $input->{user_id} ? $user_rs->find($input->{user_id}) - : $input->{email} ? $user_rs->find_by_email($input->{email}) - : undef; - if (not $user) { - $c->log->debug('Could not find user '.$input->@{qw(user_id email)}); - return $c->status(404); - } - - $c->stash('target_user', $user); + my $user = $c->stash('target_user'); my $build_name = $c->stash('build_name') // $c->stash('build_rs')->get_column('name')->single; # check if the user already has access to this build diff --git a/lib/Conch/Controller/Organization.pm b/lib/Conch/Controller/Organization.pm index f37406dc5..96dd3982d 100644 --- a/lib/Conch/Controller/Organization.pm +++ b/lib/Conch/Controller/Organization.pm @@ -213,16 +213,7 @@ sub add_user ($c) { my $input = $c->validate_request('OrganizationAddUser'); return if not $input; - my $user_rs = $c->db_user_accounts->active; - my $user = $input->{user_id} ? $user_rs->find($input->{user_id}) - : $input->{email} ? $user_rs->find_by_email($input->{email}) - : undef; - if (not $user) { - $c->log->debug('Could not find user '.$input->@{qw(user_id email)}); - return $c->status(404); - } - - $c->stash('target_user', $user); + my $user = $c->stash('target_user'); my $organization_name = $c->stash('organization_name') // $c->stash('organization_rs')->get_column('name')->single; # check if the user already has access to this organization diff --git a/lib/Conch/Controller/User.pm b/lib/Conch/Controller/User.pm index 10c98ce1c..42ebf4f14 100644 --- a/lib/Conch/Controller/User.pm +++ b/lib/Conch/Controller/User.pm @@ -40,7 +40,7 @@ sub find_user ($c) { if ($user->deactivated) { return $c->status(410, { error => 'user is deactivated', - user => { map +($_ => $user->$_), qw(id email name created deactivated) }, + $c->is_system_admin ? ( user => { map +($_ => $user->$_), qw(id email name created deactivated) } ) : (), }); } diff --git a/lib/Conch/Controller/WorkspaceUser.pm b/lib/Conch/Controller/WorkspaceUser.pm index 707b237b2..9933617c2 100644 --- a/lib/Conch/Controller/WorkspaceUser.pm +++ b/lib/Conch/Controller/WorkspaceUser.pm @@ -68,15 +68,10 @@ sub add_user ($c) { my $input = $c->validate_request('WorkspaceAddUser'); return if not $input; - my $user_rs = $c->db_user_accounts->active; - my $user = $input->{user_id} ? $user_rs->find($input->{user_id}) - : $input->{email} ? $user_rs->find_by_email($input->{email}) - : undef; - return $c->status(404) if not $user; + my $user = $c->stash('target_user'); return $c->status(204) if $user->is_admin; - $c->stash('target_user', $user); my $workspace_id = $c->stash('workspace_id'); # check if the user already has access to this workspace (whether directly or through a diff --git a/lib/Conch/Route.pm b/lib/Conch/Route.pm index ea652bf0b..c93cc2039 100644 --- a/lib/Conch/Route.pm +++ b/lib/Conch/Route.pm @@ -57,7 +57,7 @@ Chainable route that aborts with HTTP 403 if the user is not a system admin. =cut $root->add_shortcut(require_system_admin => sub ($r) { - $r->any(sub ($c) { + $r->under('/', sub ($c) { return $c->status(401) if not $c->stash('user') or not $c->stash('user_id'); @@ -67,7 +67,26 @@ Chainable route that aborts with HTTP 403 if the user is not a system admin. } return 1; - })->under; + }); + }); + +=head2 find_user_from_payload + +Chainable route that looks up the user by C or C in the JSON payload, +aborting with HTTP 410 or HTTP 404 if not found. + +=cut + + # provides a route to chain to that looks up the user provided in the payload + $root->add_shortcut(find_user_from_payload => sub ($r) { + $r->under('/', sub ($c) { + my $input = $c->validate_request('UserIdOrEmail'); + return if not $input; + + $c->stash('target_user_id_or_email', $input->{user_id} // $input->{email}); + return 1; + }) + ->under('/')->to('user#find_user'); }); # allow routes to be specified as, e.g. ->get('/')->to(...) diff --git a/lib/Conch/Route/Build.pm b/lib/Conch/Route/Build.pm index bee781f96..4a260826d 100644 --- a/lib/Conch/Route/Build.pm +++ b/lib/Conch/Route/Build.pm @@ -50,7 +50,7 @@ sub routes { $with_build_admin->get('/user')->to('#get_users'); # POST /build/:build_id_or_name/user?send_mail=<1|0> - $with_build_admin->post('/user')->to('#add_user'); + $with_build_admin->find_user_from_payload->post('/user')->to('build#add_user'); # DELETE /build/:build_id_or_name/user/#target_user_id_or_email?send_mail=<1|0> $with_build_admin diff --git a/lib/Conch/Route/Organization.pm b/lib/Conch/Route/Organization.pm index 44b80b60f..dc8171403 100644 --- a/lib/Conch/Route/Organization.pm +++ b/lib/Conch/Route/Organization.pm @@ -44,7 +44,7 @@ sub routes { $with_organization->require_system_admin->delete('/')->to('#delete'); # POST /organization/:organization_id_or_name/user?send_mail=<1|0> - $with_organization->post('/user')->to('#add_user'); + $with_organization->find_user_from_payload->post('/user')->to('organization#add_user'); # DELETE /organization/:organization_id_or_name/user/#target_user_id_or_email?send_mail=<1|0> $with_organization->under('/user/#target_user_id_or_email')->to('user#find_user') diff --git a/lib/Conch/Route/Workspace.pm b/lib/Conch/Route/Workspace.pm index d13ffdc90..5294378bd 100644 --- a/lib/Conch/Route/Workspace.pm +++ b/lib/Conch/Route/Workspace.pm @@ -71,7 +71,7 @@ sub routes { $with_workspace_admin->get('/user')->to('workspace_user#get_all'); # POST /workspace/:workspace_id_or_name/user?send_mail=<1|0> - $with_workspace_admin->post('/user')->to('workspace_user#add_user'); + $with_workspace_admin->find_user_from_payload->post('/user')->to('workspace_user#add_user'); # DELETE /workspace/:workspace_id_or_name/user/#target_user_id_or_email?send_mail=<1|0> $with_workspace_admin->under('/user/#target_user_id_or_email')->to('user#find_user') ->delete('/')->to('workspace_user#remove'); From 3029b7951b92753a3f7d8b0b09599f440475e614 Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Tue, 14 Jan 2020 10:37:14 -0800 Subject: [PATCH 07/18] remove use of possibly-undefined variable as per 272e950e49 -- $c->stash('exception') should always be populated now --- lib/Conch/Plugin/Rollbar.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Conch/Plugin/Rollbar.pm b/lib/Conch/Plugin/Rollbar.pm index 3d4fef41d..57a80eb59 100644 --- a/lib/Conch/Plugin/Rollbar.pm +++ b/lib/Conch/Plugin/Rollbar.pm @@ -39,8 +39,7 @@ sub register ($self, $app, $config) { $app->hook(before_render => sub ($c, $args) { my $template = $args->{template}; - if (my $exception = $c->stash('exception') - or ($template and $template =~ /exception/)) { + if (my $exception = $c->stash('exception')) { my $rollbar_id = $c->send_exception_to_rollbar($exception); $c->log->debug('exception sent to rollbar: id '.$rollbar_id) if $rollbar_id; } From 14cf848760c0f648d50313f18694b7dece047d11 Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Tue, 14 Jan 2020 15:39:56 -0800 Subject: [PATCH 08/18] switch GET /device/:id ETag to weak form Mojolicious 8.31 contains https://github.com/mojolicious/mojo/pull/1420 \o/ --- cpanfile | 2 +- cpanfile.snapshot | 6 +++--- lib/Conch/Controller/Device.pm | 3 +-- t/integration/crud/devices.t | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cpanfile b/cpanfile index ebdb69427..febd7ab10 100644 --- a/cpanfile +++ b/cpanfile @@ -33,7 +33,7 @@ requires 'Net::DNS'; # not used directly, but Email::Valid sometimes demands requires 'experimental', '0.020'; # mojolicious and networking -requires 'Mojolicious', '8.15'; +requires 'Mojolicious', '8.31'; requires 'Mojo::Pg'; requires 'Mojo::JWT'; requires 'Mojolicious::Plugin::Util::RandomString', '0.07'; # memory leak: https://rt.cpan.org/Ticket/Display.html?id=125981 diff --git a/cpanfile.snapshot b/cpanfile.snapshot index d98d1cc1a..62ef0b4f9 100644 --- a/cpanfile.snapshot +++ b/cpanfile.snapshot @@ -2679,8 +2679,8 @@ DISTRIBUTIONS Mojolicious 8.03 SQL::Abstract 1.86 perl 5.010001 - Mojolicious-8.26 - pathname: S/SR/SRI/Mojolicious-8.26.tar.gz + Mojolicious-8.31 + pathname: S/SR/SRI/Mojolicious-8.31.tar.gz provides: Mojo undef Mojo::Asset undef @@ -2749,7 +2749,7 @@ DISTRIBUTIONS Mojo::UserAgent::Transactor undef Mojo::Util undef Mojo::WebSocket undef - Mojolicious 8.26 + Mojolicious 8.31 Mojolicious::Command undef Mojolicious::Command::Author::cpanify undef Mojolicious::Command::Author::generate undef diff --git a/lib/Conch/Controller/Device.pm b/lib/Conch/Controller/Device.pm index 958c23ea7..5f2953eb4 100644 --- a/lib/Conch/Controller/Device.pm +++ b/lib/Conch/Controller/Device.pm @@ -135,8 +135,7 @@ reflected in the checksum. sub get ($c) { # allow the (authenticated) client to cache the result based on updated time my $etag = Digest::MD5::md5_hex($c->stash('device_rs')->get_column('updated')->single); - # TODO: this is really a weak etag. requires https://github.com/mojolicious/mojo/pull/1420 - return $c->status(304) if $c->is_fresh(etag => $etag); + return $c->status(304) if $c->is_fresh(etag => qq{W/"$etag"}); my $device = $c->stash('device_rs')->with_sku->with_build_name->single; my $latest_report = $c->stash('device_rs')->latest_device_report->get_column('report')->single; diff --git a/t/integration/crud/devices.t b/t/integration/crud/devices.t index 631678e51..61d145f64 100644 --- a/t/integration/crud/devices.t +++ b/t/integration/crud/devices.t @@ -634,7 +634,7 @@ subtest 'caching' => sub { $t->get_ok('/device/TEST') ->status_is(200) ->header_is('Cache-Control', 'no-cache') - ->header_exists('ETag') + ->header_like('ETag', qr{^W/"[^"]+"$}) ->json_schema_is('DetailedDevice') ->json_is($detailed_device); From 9c59928ae96970fe2504a676ab3dd0c80fbcea3f Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Tue, 14 Jan 2020 16:17:35 -0800 Subject: [PATCH 09/18] clean up uses of the application config and prevent plugins from seeing the secrets used for data encryption --- lib/Conch.pm | 10 +++++----- lib/Conch/Command/copy_user_data.pm | 2 +- lib/Conch/Command/create_token.pm | 2 +- lib/Conch/Controller/Login.pm | 2 +- lib/Conch/Controller/User.pm | 2 +- lib/Conch/Controller/WorkspaceDevice.pm | 6 +++--- lib/Conch/Plugin/Rollbar.pm | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/Conch.pm b/lib/Conch.pm index cdea751a5..b0cc62847 100644 --- a/lib/Conch.pm +++ b/lib/Conch.pm @@ -35,12 +35,12 @@ and therefore other helpers). sub startup { my $self = shift; - # Configuration - $self->plugin('Config'); - $self->secrets($self->config('secrets')); $self->sessions->cookie_name('conch'); $self->sessions->default_expiration(2592000); # 30 days + $self->plugin('Config'); + $self->secrets(delete $self->config->{secrets}); + $self->plugin('Conch::Plugin::Features', $self->config); $self->plugin('Conch::Plugin::Logging', $self->config); $self->plugin('Conch::Plugin::GitVersion', $self->config); @@ -65,7 +65,7 @@ sub startup { if (ref $args->{json} eq 'ARRAY' # TODO: skip if ?page_size is passed (and we actually used it). - and $args->{json}->@* >= ($c->app->config->{rollbar}{warn_payload_elements} // 35) + and $args->{json}->@* >= (($c->app->config('rollbar')//{})->{warn_payload_elements} // 35) and $c->feature('rollbar')) { my $endpoint = join '#', map $_//'', ($c->match->stack->[-1]//{})->@{qw(controller action)}; $c->send_message_to_rollbar( @@ -79,7 +79,7 @@ sub startup { # do this after the response has been sent $c->on(finish => sub ($c) { my $body_size = $c->res->body_size; - if ($body_size >= ($c->app->config->{rollbar}{warn_payload_size} // 10000) + if ($body_size >= (($c->app->config('rollbar')//{})->{warn_payload_size} // 10000) and $c->feature('rollbar')) { my $endpoint = join '#', map $_//'', ($c->match->stack->[-1]//{})->@{qw(controller action)}; $c->send_message_to_rollbar( diff --git a/lib/Conch/Command/copy_user_data.pm b/lib/Conch/Command/copy_user_data.pm index 1c2f4f9fe..10801d95e 100644 --- a/lib/Conch/Command/copy_user_data.pm +++ b/lib/Conch/Command/copy_user_data.pm @@ -58,7 +58,7 @@ sub run ($self, @opts) { my $app = $self->app; my $app_name = join(' ', $app->moniker, 'copy_user_data', $app->version_tag, '('.$$.')'); - my $db_credentials = Conch::DB::Util::get_credentials($app->config->{database}, $app->log); + my $db_credentials = Conch::DB::Util::get_credentials($app->config('database'), $app->log); my ($from_schema, $to_schema) = map Conch::DB->connect( $db_credentials->{dsn} =~ s/(?<=dbi:Pg:dbname=)([^;]+)(?=;host=)/$_/r, diff --git a/lib/Conch/Command/create_token.pm b/lib/Conch/Command/create_token.pm index 33ea20d25..ac71e400a 100644 --- a/lib/Conch/Command/create_token.pm +++ b/lib/Conch/Command/create_token.pm @@ -39,7 +39,7 @@ sub run ($self, @opts) { my $user = $self->app->db_user_accounts->active->find_by_email($opt->email); die 'cannot find user with email ', $opt->email if not $user; - my $expires_abs = time + (($self->app->config('jwt') || {})->{custom_token_expiry} // 86400*365*5); + my $expires_abs = time + (($self->app->config('jwt')//{})->{custom_token_expiry} // 86400*365*5); my ($token, $jwt) = $self->app->generate_jwt($user->id, $expires_abs, $opt->name); say $jwt; } diff --git a/lib/Conch/Controller/Login.pm b/lib/Conch/Controller/Login.pm index 89ef603aa..8fd0b66da 100644 --- a/lib/Conch/Controller/Login.pm +++ b/lib/Conch/Controller/Login.pm @@ -22,7 +22,7 @@ Create a response containing a login JWT, which the user should later present in =cut sub _respond_with_jwt ($c, $user_id, $expires_delta = undef) { - my $jwt_config = $c->app->config('jwt') || {}; + my $jwt_config = $c->app->config('jwt') // {}; my $expires_abs = time + ( defined $expires_delta ? $expires_delta diff --git a/lib/Conch/Controller/User.pm b/lib/Conch/Controller/User.pm index 42ebf4f14..64f064b6d 100644 --- a/lib/Conch/Controller/User.pm +++ b/lib/Conch/Controller/User.pm @@ -610,7 +610,7 @@ sub create_api_token ($c) { my $user = $c->stash('target_user'); # default expiration: 5 years - my $expires_abs = time + (($c->app->config('jwt') || {})->{custom_token_expiry} // 86400*365*5); + my $expires_abs = time + (($c->app->config('jwt')//{})->{custom_token_expiry} // 86400*365*5); my ($token, $jwt) = $c->generate_jwt($user->id, $expires_abs, $input->{name}); return if $c->res->code; diff --git a/lib/Conch/Controller/WorkspaceDevice.pm b/lib/Conch/Controller/WorkspaceDevice.pm index e950ec5d3..e2336c982 100644 --- a/lib/Conch/Controller/WorkspaceDevice.pm +++ b/lib/Conch/Controller/WorkspaceDevice.pm @@ -130,9 +130,9 @@ sub device_totals ($c) { } return $c->reply->not_found if not $workspace; - my %switch_aliases = map +($_ => 1), $c->app->config->{switch_aliases}->@*; - my %storage_aliases = map +($_ => 1), $c->app->config->{storage_aliases}->@*; - my %compute_aliases = map +($_ => 1), $c->app->config->{compute_aliases}->@*; + my %switch_aliases = map +($_ => 1), ($c->app->config('switch_aliases')//{})->@*; + my %storage_aliases = map +($_ => 1), ($c->app->config('storage_aliases')//{})->@*; + my %compute_aliases = map +($_ => 1), ($c->app->config('compute_aliases')//{})->@*; my @counts = $workspace ->related_resultset('workspace_racks') diff --git a/lib/Conch/Plugin/Rollbar.pm b/lib/Conch/Plugin/Rollbar.pm index 57a80eb59..62b257b53 100644 --- a/lib/Conch/Plugin/Rollbar.pm +++ b/lib/Conch/Plugin/Rollbar.pm @@ -99,7 +99,7 @@ thus created. my $notifier; $app->helper(send_exception_to_rollbar => sub ($c, $exception) { - $notifier //= _create_notifier($c->app, $c->config); + $notifier //= _create_notifier($c->app, $c->app->config); return if not $notifier; my @frames = map +{ @@ -159,7 +159,7 @@ A string or data structure of fingerprint data for grouping occurrences is optio Carp::croak('severity must be one of: '.join(', ',@message_levels)) if !$ENV{MOJO_MODE} and none { $severity eq $_ } @message_levels; - $notifier //= _create_notifier($c->app, $c->config); + $notifier //= _create_notifier($c->app, $c->app->config); return if not $notifier; my $rollbar_id = create_uuid_str(); From a3cf30788e5cec077dcbd57e9f93924fe0bf4fd0 Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Wed, 15 Jan 2020 15:04:33 -0800 Subject: [PATCH 10/18] actually test using two JWTs at the same time --- t/integration/users.t | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/t/integration/users.t b/t/integration/users.t index ed97ea497..258778029 100644 --- a/t/integration/users.t +++ b/t/integration/users.t @@ -266,11 +266,18 @@ subtest 'User' => sub { }, $super_user_data, $user_detailed), ]); - # get another JWT + is($ro_user->related_resultset('user_session_tokens')->count, 1, 'just 1 token presently'); + + # make the token look really old (not yet expired, but close to it) + $ro_user->related_resultset('user_session_tokens')->update({ created => '2000-01-01' }); + $ro_user->update({ password => '123' }); $t->post_ok('/login', json => { email => $ro_user->email, password => '123' }) ->status_is(200); push @login_token, $t->tx->res->json->{jwt_token}; + + is($ro_user->related_resultset('user_session_tokens')->count, 2, 'a new token was created'); + { my $t2 = Test::Conch->new(pg => $t->pg); $t2->get_ok('/user/me', { Authorization => 'Bearer '.$login_token[1] }) From b379b5326b02b91665db06349f27664beddc1b26 Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Thu, 16 Jan 2020 14:17:28 -0800 Subject: [PATCH 11/18] test exactly which auth failure occurred --- t/integration/users.t | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/t/integration/users.t b/t/integration/users.t index 258778029..4caf8aa3c 100644 --- a/t/integration/users.t +++ b/t/integration/users.t @@ -226,7 +226,9 @@ subtest 'User' => sub { { (map +($_ => $build1->$_), qw(id name description)), role => 'rw', role_via_organization_id => $organization->id }, { (map +($_ => $build2->$_), qw(id name description)), role => 'ro', role_via_organization_id => $organization->id }, ], - }); + }) + ->log_debug_is('attempting to authenticate with Authorization: Bearer header...'); + ; $user_detailed = $t2->tx->res->json; # the superuser always sees parent workspace ids $user_detailed->{workspaces}[0]{parent_workspace_id} = $child_ws->parent_workspace_id; @@ -324,12 +326,14 @@ subtest 'User' => sub { { my $t2 = Test::Conch->new(pg => $t->pg); $t2->get_ok('/user/me', { Authorization => 'Bearer '.$login_token[0] }) - ->status_is(401, 'main login token no longer works after changing password'); + ->status_is(401, 'main login token no longer works after changing password') + ->log_debug_is('auth failed: JWT for user_id '.$ro_user->id.' could not be found'); } { my $t2 = Test::Conch->new(pg => $t->pg); $t2->get_ok('/user/me', { Authorization => 'Bearer '.$login_token[1] }) - ->status_is(401, 'second login token no longer works after changing password'); + ->status_is(401, 'second login token no longer works after changing password') + ->log_debug_is('auth failed: JWT for user_id '.$ro_user->id.' could not be found'); } { @@ -355,7 +359,8 @@ subtest 'User' => sub { { my $t2 = Test::Conch->new(pg => $t->pg); $t2->get_ok('/user/me', { Authorization => 'Bearer '.$api_token }) - ->status_is(401, 'api login token no longer works either'); + ->status_is(401, 'api login token no longer works either') + ->log_debug_is('auth failed: JWT for user_id '.$ro_user->id.' could not be found'); } $t->post_ok('/login', json => { user_id => $user_detailed->{id}, password => 'another password' }) @@ -383,7 +388,8 @@ subtest 'Log out' => sub { $t->post_ok('/logout') ->status_is(204); $t->get_ok('/workspace') - ->status_is(401); + ->status_is(401) + ->log_debug_is('auth failed: no credentials provided'); }; subtest 'JWT authentication' => sub { @@ -411,8 +417,8 @@ subtest 'JWT authentication' => sub { expires => $jwt_claims->{exp}, )->encode; $t->get_ok('/workspace', { Authorization => 'Bearer '.$hacked_jwt_token }) - ->status_is(401, 'the user_id is verified in the JWT'); - $t->log_debug_is('auth failed: JWT for user_id '.$bad_user_id.' could not be found'); + ->status_is(401) + ->log_debug_is('auth failed: JWT for user_id '.$bad_user_id.' could not be found'); $t->post_ok('/refresh_token', { Authorization => 'Bearer '.$jwt_token }) ->status_is(200) @@ -421,11 +427,9 @@ subtest 'JWT authentication' => sub { my $new_jwt_token = $t->tx->res->json->{jwt_token}; $t->get_ok('/workspace', { Authorization => 'Bearer '.$new_jwt_token }) ->status_is(200, 'Can authenticate with new token'); - $t->get_ok('/workspace', { Authorization => 'Bearer '.$jwt_token }) - ->status_is(401, 'Cannot use old token'); - $t->get_ok('/me', { Authorization => 'Bearer '.$jwt_token }) - ->status_is(401, 'Cannot reuse old JWT'); + ->status_is(401, 'Cannot use old token') + ->log_debug_is('auth failed: JWT for user_id '.$ro_user->id.' could not be found'); $t_super->get_ok('/me', { Authorization => 'Bearer '.$new_jwt_token }) ->status_is(401, 'cannot use other user\'s JWT') @@ -456,7 +460,9 @@ subtest 'JWT authentication' => sub { ]); $t->get_ok('/workspace', { Authorization => "Bearer $new_jwt_token" }) - ->status_is(401, 'Cannot use after user revocation'); + ->status_is(401, 'Cannot use token or session after user revocation') + ->log_debug_is('auth failed: JWT for user_id '.$ro_user->id.' could not be found'); + $t->post_ok('/refresh_token', { Authorization => "Bearer $new_jwt_token" }) ->status_is(401, 'Cannot use after user revocation'); From dfe2e116a2bbded5c896982efacc8049627c773e Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Wed, 15 Jan 2020 11:36:04 -0800 Subject: [PATCH 12/18] do not log use of session when JWT is also set and being used --- docs/modules/Conch::Controller::Login.md | 3 +-- lib/Conch/Controller/Login.pm | 10 ++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/modules/Conch::Controller::Login.md b/docs/modules/Conch::Controller::Login.md index b445f9c4a..ee36e0322 100644 --- a/docs/modules/Conch::Controller::Login.md +++ b/docs/modules/Conch::Controller::Login.md @@ -14,9 +14,8 @@ Create a response containing a login JWT, which the user should later present in Handle the details of authenticating the user, with one of the following options: ``` -* existing session for the user * signed JWT in the Authorization Bearer header -* Old 'conch' session cookie +* existing session for the user (using the 'conch' session cookie) ``` Does not terminate the connection if authentication is successful, allowing for chaining to diff --git a/lib/Conch/Controller/Login.pm b/lib/Conch/Controller/Login.pm index 8fd0b66da..7907ed142 100644 --- a/lib/Conch/Controller/Login.pm +++ b/lib/Conch/Controller/Login.pm @@ -48,9 +48,8 @@ sub _respond_with_jwt ($c, $user_id, $expires_delta = undef) { Handle the details of authenticating the user, with one of the following options: - * existing session for the user * signed JWT in the Authorization Bearer header - * Old 'conch' session cookie + * existing session for the user (using the 'conch' session cookie) Does not terminate the connection if authentication is successful, allowing for chaining to subsequent routes and actions. @@ -111,8 +110,11 @@ sub authenticate ($c) { if ($c->session('user')) { return $c->status(401, { error => 'user session is invalid' }) if not is_uuid($c->session('user')) or ($user_id and $c->session('user') ne $user_id); - $c->log->debug('using session user='.$c->session('user')); - $user_id ||= $c->session('user'); + + if (not $user_id) { + $user_id = $c->session('user'); + $c->log->debug('using session user='.$user_id); + } } # clear out all expired session tokens From 6ab9926fa015988d9ad8af9815b6057180641228 Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Tue, 14 Jan 2020 16:25:02 -0800 Subject: [PATCH 13/18] allow configuring the expiration time for persistent session cookies --- conch.conf.dist | 6 ++- docs/modules/Conch::Controller::Login.md | 2 +- lib/Conch.pm | 2 +- lib/Conch/Command/create_token.pm | 2 +- lib/Conch/Controller/Login.pm | 63 +++++++++++++++--------- lib/Conch/Controller/User.pm | 2 +- 6 files changed, 49 insertions(+), 28 deletions(-) diff --git a/conch.conf.dist b/conch.conf.dist index 4bee6e0bb..28e5b66ea 100644 --- a/conch.conf.dist +++ b/conch.conf.dist @@ -5,10 +5,12 @@ # old ones. secrets => ["hunter2"], - jwt => { - # time in seconds for a JWT to be valid before requiring refresh or re-auth + authentication => { + # time in seconds for a login token and/or persistent session cookie to be valid before requiring refresh or re-auth system_admin_expiry => 2592000, # 30 days normal_expiry => 86400, # 1 day + + # used for api tokens only custom_token_expiry => 86400*365*5, # 5 years }, diff --git a/docs/modules/Conch::Controller::Login.md b/docs/modules/Conch::Controller::Login.md index ee36e0322..a9814742c 100644 --- a/docs/modules/Conch::Controller::Login.md +++ b/docs/modules/Conch::Controller::Login.md @@ -32,7 +32,7 @@ Logs a user out by expiring their session ## refresh\_token -Refresh a user's JWT token. Deletes the old token and expires the session. +Refresh a user's JWT token and persistent user session, deleting the old token. # LICENSING diff --git a/lib/Conch.pm b/lib/Conch.pm index b0cc62847..b1b48a0ee 100644 --- a/lib/Conch.pm +++ b/lib/Conch.pm @@ -36,7 +36,7 @@ sub startup { my $self = shift; $self->sessions->cookie_name('conch'); - $self->sessions->default_expiration(2592000); # 30 days + $self->sessions->default_expiration(86400); # 1 day $self->plugin('Config'); $self->secrets(delete $self->config->{secrets}); diff --git a/lib/Conch/Command/create_token.pm b/lib/Conch/Command/create_token.pm index ac71e400a..a7e1b87a5 100644 --- a/lib/Conch/Command/create_token.pm +++ b/lib/Conch/Command/create_token.pm @@ -39,7 +39,7 @@ sub run ($self, @opts) { my $user = $self->app->db_user_accounts->active->find_by_email($opt->email); die 'cannot find user with email ', $opt->email if not $user; - my $expires_abs = time + (($self->app->config('jwt')//{})->{custom_token_expiry} // 86400*365*5); + my $expires_abs = time + (($self->app->config('authentication')//{})->{custom_token_expiry} // 86400*365*5); my ($token, $jwt) = $self->app->generate_jwt($user->id, $expires_abs, $opt->name); say $jwt; } diff --git a/lib/Conch/Controller/Login.pm b/lib/Conch/Controller/Login.pm index 7907ed142..44e8158c4 100644 --- a/lib/Conch/Controller/Login.pm +++ b/lib/Conch/Controller/Login.pm @@ -21,26 +21,17 @@ Create a response containing a login JWT, which the user should later present in =cut -sub _respond_with_jwt ($c, $user_id, $expires_delta = undef) { - my $jwt_config = $c->app->config('jwt') // {}; - - my $expires_abs = time + ( - defined $expires_delta ? $expires_delta - # system admin default: 30 days - : $c->is_system_admin ? ($jwt_config->{system_admin_expiry} || 2592000) - # normal default: 1 day - : ($jwt_config->{normal_expiry} || 86400)); - +sub _respond_with_jwt ($c, $user_id, $expires_epoch) { my ($session_token, $jwt) = $c->generate_jwt( $user_id, - $expires_abs, + $expires_epoch, 'login_jwt_'.join('_', Time::HiRes::gettimeofday), # reasonably unique name ); return if $c->res->code; - $c->res->headers->last_modified(Mojo::Date->new($session_token->created->epoch)); - $c->res->headers->expires(Mojo::Date->new($session_token->expires->epoch)); + $c->res->headers->last_modified(Mojo::Date->new(time)); + $c->res->headers->expires(Mojo::Date->new($expires_epoch)); return $c->status(200, { jwt_token => $jwt }); } @@ -188,8 +179,6 @@ sub login ($c) { $c->stash('user_id', $user->id); $c->stash('user', $user); - $c->session(user => $user->id) if not $c->feature('stop_conch_cookie_issue'); - # clear out all expired session tokens $c->db_user_session_tokens->expired->delete; @@ -201,11 +190,18 @@ sub login ($c) { password => Authen::Passphrase::RejectAll->new, # ensure password cannot be used again }); # password must be reset within 10 minutes - $c->session(expires => time + 10 * 60); + + if (not $c->feature('stop_conch_cookie_issue')) { + $c->session('user', $user->id); + $c->session('expires', time + 10 * 60); + } + else { + $c->session('expires', 1); + } # we logged the user in, but he must now change his password (within 10 minutes) $c->res->headers->location($c->url_for('/user/me/password')); - return $c->_respond_with_jwt($user->id, 10 * 60); + return $c->_respond_with_jwt($user->id, time + 10 * 60); } $c->log->info('user '.$user->name.' ('.$user->email.') logged in'); @@ -227,10 +223,26 @@ sub login ($c) { if (my $token = $token_rs->order_by({ -desc => 'created' })->rows(1)->single) { $c->res->headers->last_modified(Mojo::Date->new($token->created->epoch)); $c->res->headers->expires(Mojo::Date->new($token->expires->epoch)); + + if (not $c->feature('stop_conch_cookie_issue')) { + $c->session('user', $user->id); + $c->session('expires', $token->expires->epoch); + } + return $c->status(200, { jwt_token => $c->generate_jwt_from_token($token) }); } - return $c->_respond_with_jwt($user->id); + my $config = $c->app->config('authentication') // {}; + my $expires_epoch = time + + ($c->is_system_admin ? ($config->{system_admin_expiry} || 2592000) # 30 days + : ($config->{normal_expiry} || 86400)); # 1 day + + if (not $c->feature('stop_conch_cookie_issue')) { + $c->session('user', $user->id); + $c->session('expires', $expires_epoch); + } + + return $c->_respond_with_jwt($user->id, $expires_epoch); } =head2 logout @@ -259,7 +271,7 @@ sub logout ($c) { =head2 refresh_token -Refresh a user's JWT token. Deletes the old token and expires the session. +Refresh a user's JWT token and persistent user session, deleting the old token. =cut @@ -267,8 +279,6 @@ sub refresh_token ($c) { $c->validate_request('Null'); return if $c->res->code; - $c->session('expires', 1) if $c->session('user'); - $c->db_user_session_tokens ->search({ id => $c->stash('token_id'), user_id => $c->stash('user_id') }) ->unexpired->expire @@ -277,7 +287,16 @@ sub refresh_token ($c) { # clear out all expired session tokens $c->db_user_session_tokens->expired->delete; - return $c->_respond_with_jwt($c->stash('user_id')); + my $config = $c->app->config('authentication') // {}; + my $expires_epoch = time + + ($c->is_system_admin ? ($config->{system_admin_expiry} || 2592000) # 30 days + : ($config->{normal_expiry} || 86400)); # 1 day + + # renew the session + $c->session('expires', $expires_epoch) + if $c->session('user') and not $c->feature('stop_conch_cookie_issue'); + + return $c->_respond_with_jwt($c->stash('user_id'), $expires_epoch); } 1; diff --git a/lib/Conch/Controller/User.pm b/lib/Conch/Controller/User.pm index 64f064b6d..dd545d9f7 100644 --- a/lib/Conch/Controller/User.pm +++ b/lib/Conch/Controller/User.pm @@ -610,7 +610,7 @@ sub create_api_token ($c) { my $user = $c->stash('target_user'); # default expiration: 5 years - my $expires_abs = time + (($c->app->config('jwt')//{})->{custom_token_expiry} // 86400*365*5); + my $expires_abs = time + (($c->app->config('authentication')//{})->{custom_token_expiry} // 86400*365*5); my ($token, $jwt) = $c->generate_jwt($user->id, $expires_abs, $input->{name}); return if $c->res->code; From 5a3626b95260b8d9c56293fd9726b537de7fcd3a Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Wed, 15 Jan 2020 13:02:33 -0800 Subject: [PATCH 14/18] pull session management out into a separate sub also ensures session stays expired when configured to not be used --- docs/modules/Conch::Controller::Login.md | 2 +- lib/Conch/Controller/Login.pm | 39 +++++++++++------------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/docs/modules/Conch::Controller::Login.md b/docs/modules/Conch::Controller::Login.md index a9814742c..c7ffc3f4f 100644 --- a/docs/modules/Conch::Controller::Login.md +++ b/docs/modules/Conch::Controller::Login.md @@ -28,7 +28,7 @@ Response uses the Login json schema, containing a JWT. ## logout -Logs a user out by expiring their session +Logs a user out by expiring their JWT and user session ## refresh\_token diff --git a/lib/Conch/Controller/Login.pm b/lib/Conch/Controller/Login.pm index 44e8158c4..75492944a 100644 --- a/lib/Conch/Controller/Login.pm +++ b/lib/Conch/Controller/Login.pm @@ -124,7 +124,7 @@ sub authenticate ($c) { $c->log->debug('attempt to authenticate before changing insecure password'); # ensure session and and all login JWTs expire in no more than 10 minutes - $c->session(expiration => 10 * 60); + $c->_update_session($user->id, time + 10 * 60); $user->user_session_tokens->login_only ->update({ expires => \'least(expires, now() + interval \'10 minutes\')' }) if $session_token; @@ -191,13 +191,7 @@ sub login ($c) { }); # password must be reset within 10 minutes - if (not $c->feature('stop_conch_cookie_issue')) { - $c->session('user', $user->id); - $c->session('expires', time + 10 * 60); - } - else { - $c->session('expires', 1); - } + $c->_update_session($user->id, time + 10 * 60); # we logged the user in, but he must now change his password (within 10 minutes) $c->res->headers->location($c->url_for('/user/me/password')); @@ -224,10 +218,7 @@ sub login ($c) { $c->res->headers->last_modified(Mojo::Date->new($token->created->epoch)); $c->res->headers->expires(Mojo::Date->new($token->expires->epoch)); - if (not $c->feature('stop_conch_cookie_issue')) { - $c->session('user', $user->id); - $c->session('expires', $token->expires->epoch); - } + $c->_update_session($user->id, $token->expires->epoch); return $c->status(200, { jwt_token => $c->generate_jwt_from_token($token) }); } @@ -237,22 +228,19 @@ sub login ($c) { ($c->is_system_admin ? ($config->{system_admin_expiry} || 2592000) # 30 days : ($config->{normal_expiry} || 86400)); # 1 day - if (not $c->feature('stop_conch_cookie_issue')) { - $c->session('user', $user->id); - $c->session('expires', $expires_epoch); - } + $c->_update_session($user->id, $expires_epoch); return $c->_respond_with_jwt($user->id, $expires_epoch); } =head2 logout -Logs a user out by expiring their session +Logs a user out by expiring their JWT and user session =cut sub logout ($c) { - $c->session(expires => 1); + $c->_update_session; # expire this user's token # (assuming we have the user's id, which we probably don't) @@ -292,13 +280,22 @@ sub refresh_token ($c) { ($c->is_system_admin ? ($config->{system_admin_expiry} || 2592000) # 30 days : ($config->{normal_expiry} || 86400)); # 1 day - # renew the session - $c->session('expires', $expires_epoch) - if $c->session('user') and not $c->feature('stop_conch_cookie_issue'); + # renew the session, if there is one + $c->_update_session($c->stash('user_id'), $expires_epoch); return $c->_respond_with_jwt($c->stash('user_id'), $expires_epoch); } +sub _update_session ($c, $user_id = undef, $expires_epoch = 0) { + if (not $user_id or not $expires_epoch or $c->feature('stop_conch_cookie_issue')) { + $c->session('expires', 1); + } + else { + $c->session('user', $user_id); + $c->session('expires', $expires_epoch); + } +} + 1; __END__ From e005b749c29d42594ab0c665e089c6c387a7df17 Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Wed, 15 Jan 2020 13:51:54 -0800 Subject: [PATCH 15/18] no longer store to the user session when logging in, by default It is expected that the JWT should be solely used by most clients, but the session can still be used with a new parameter passed to POST /login. --- docs/json-schema/request.json | 4 +++ docs/modules/Test::Conch.md | 4 +-- json-schema/request.yaml | 3 +++ lib/Conch/Controller/Login.pm | 10 +++---- lib/Test/Conch.pm | 7 ++--- t/integration/users.t | 50 ++++++++++++++++++++++++++--------- t/schema.t | 1 + 7 files changed, 56 insertions(+), 23 deletions(-) diff --git a/docs/json-schema/request.json b/docs/json-schema/request.json index a5389f6ce..09cfec2a8 100644 --- a/docs/json-schema/request.json +++ b/docs/json-schema/request.json @@ -657,6 +657,10 @@ "password" : { "$ref" : "common.json#/definitions/non_empty_string" }, + "set_session" : { + "default" : false, + "type" : "boolean" + }, "user_id" : { "$ref" : "common.json#/definitions/uuid" } diff --git a/docs/modules/Test::Conch.md b/docs/modules/Test::Conch.md index 79423c233..c32860ca6 100644 --- a/docs/modules/Test::Conch.md +++ b/docs/modules/Test::Conch.md @@ -176,8 +176,8 @@ See ["\_generate\_definition" in Test::Conch::Fixtures](../modules/Test%3A%3ACon Authenticates a user in the current test instance. Uses default (superuser) credentials if not provided. Optionally will bail out of **all** tests on failure. -This will set 'user' in the session (`$t->app->session('user')`), so a token is not needed -on subsequent requests. +This will set 'user' in the session (`$t->ua->cookie_jar`, accessed internally via +`$c->session('user')`), so a token is not needed on subsequent requests. ## txn\_local diff --git a/json-schema/request.yaml b/json-schema/request.yaml index 0e3e0972f..95a529b2f 100644 --- a/json-schema/request.yaml +++ b/json-schema/request.yaml @@ -369,6 +369,9 @@ definitions: $ref: common.yaml#/definitions/email_address password: $ref: common.yaml#/definitions/non_empty_string + set_session: + type: boolean + default: false UserIdOrEmail: type: object additionalProperties: true diff --git a/lib/Conch/Controller/Login.pm b/lib/Conch/Controller/Login.pm index 75492944a..50a2bd9a3 100644 --- a/lib/Conch/Controller/Login.pm +++ b/lib/Conch/Controller/Login.pm @@ -123,8 +123,8 @@ sub authenticate ($c) { if ($c->req->url ne '/user/me/password') { $c->log->debug('attempt to authenticate before changing insecure password'); - # ensure session and and all login JWTs expire in no more than 10 minutes - $c->_update_session($user->id, time + 10 * 60); + # ensure session and all login JWTs expire in no more than 10 minutes + $c->_update_session($c->session('user'), time + 10 * 60); $user->user_session_tokens->login_only ->update({ expires => \'least(expires, now() + interval \'10 minutes\')' }) if $session_token; @@ -191,7 +191,7 @@ sub login ($c) { }); # password must be reset within 10 minutes - $c->_update_session($user->id, time + 10 * 60); + $c->_update_session($user->id, $input->{set_session} ? time + 10 * 60 : 0); # we logged the user in, but he must now change his password (within 10 minutes) $c->res->headers->location($c->url_for('/user/me/password')); @@ -218,7 +218,7 @@ sub login ($c) { $c->res->headers->last_modified(Mojo::Date->new($token->created->epoch)); $c->res->headers->expires(Mojo::Date->new($token->expires->epoch)); - $c->_update_session($user->id, $token->expires->epoch); + $c->_update_session($user->id, $input->{set_session} ? $token->expires->epoch : 0); return $c->status(200, { jwt_token => $c->generate_jwt_from_token($token) }); } @@ -228,7 +228,7 @@ sub login ($c) { ($c->is_system_admin ? ($config->{system_admin_expiry} || 2592000) # 30 days : ($config->{normal_expiry} || 86400)); # 1 day - $c->_update_session($user->id, $expires_epoch); + $c->_update_session($user->id, $input->{set_session} ? $expires_epoch : 0); return $c->_respond_with_jwt($user->id, $expires_epoch); } diff --git a/lib/Test/Conch.pm b/lib/Test/Conch.pm index 7803347e9..9d711ce64 100644 --- a/lib/Test/Conch.pm +++ b/lib/Test/Conch.pm @@ -502,8 +502,8 @@ sub generate_fixtures ($self, @specification) { Authenticates a user in the current test instance. Uses default (superuser) credentials if not provided. Optionally will bail out of B tests on failure. -This will set 'user' in the session (C<< $t->app->session('user') >>), so a token is not needed -on subsequent requests. +This will set 'user' in the session (C<< $t->ua->cookie_jar >>, accessed internally via +C<< $c->session('user') >>), so a token is not needed on subsequent requests. =cut @@ -511,9 +511,10 @@ sub authenticate ($self, %args) { $args{bailout} //= 1 if not $args{email}; $args{email} //= CONCH_EMAIL; $args{password} //= CONCH_PASSWORD; # note that if a fixture is used, everything is accepted (for speed) + $args{set_session} //= JSON::PP::true; local $Test::Builder::Level = $Test::Builder::Level + 1; - $self->post_ok('/login', json => { %args{qw(email password)} }) + $self->post_ok('/login', json => { %args{qw(email password set_session)} }) ->status_is(200, $args{message} // 'logged in as '.$args{email}) or $args{bailout} and Test::More::BAIL_OUT('Failed to log in as '.$args{email}); diff --git a/t/integration/users.t b/t/integration/users.t index 4caf8aa3c..9f95af56b 100644 --- a/t/integration/users.t +++ b/t/integration/users.t @@ -274,12 +274,30 @@ subtest 'User' => sub { $ro_user->related_resultset('user_session_tokens')->update({ created => '2000-01-01' }); $ro_user->update({ password => '123' }); - $t->post_ok('/login', json => { email => $ro_user->email, password => '123' }) + $t->post_ok('/login', json => { email => $ro_user->email, password => '123', set_session => JSON::PP::false }) ->status_is(200); push @login_token, $t->tx->res->json->{jwt_token}; is($ro_user->related_resultset('user_session_tokens')->count, 2, 'a new token was created'); + is($t->ua->cookie_jar->all->[0]->expires, 1, 'session cookie is expired'); + + $t->get_ok('/user/me') + ->status_is(401) + ->log_debug_is('auth failed: no credentials provided'); + + $t->post_ok('/login', json => { email => $ro_user->email, password => '123', set_session => JSON::PP::true }) + ->status_is(200) + ->json_schema_is('Login'); + + is($ro_user->related_resultset('user_session_tokens')->count, 2, 'got second login token again'); + + cmp_deeply( + $t->ua->cookie_jar->all->[0]->expires, + within_tolerance(time + 60*60*24, plus_or_minus => 10), + 'session expires approximately 1 day in the future', + ); + { my $t2 = Test::Conch->new(pg => $t->pg); $t2->get_ok('/user/me', { Authorization => 'Bearer '.$login_token[1] }) @@ -353,7 +371,8 @@ subtest 'User' => sub { ->status_is(200) ->log_info_is('user rO_USer ('.$ro_user->email.') logged in'); - $t->post_ok('/user/me/password?clear_tokens=all', json => { password => 'another password' }) + $t->post_ok('/user/me/password?clear_tokens=all', { Authorization => 'Bearer '.$t->tx->res->json->{jwt_token} }, + json => { password => 'another password' }) ->status_is(204, 'changed password again'); { @@ -366,13 +385,14 @@ subtest 'User' => sub { $t->post_ok('/login', json => { user_id => $user_detailed->{id}, password => 'another password' }) ->status_is(200, 'logged in using second new password, and user_id instead of email'); - $t->post_ok('/user/me/password', json => { password => '123' }) + $t->post_ok('/user/me/password', { Authorization => 'Bearer '.$t->tx->res->json->{jwt_token} }, + json => { password => '123' }) ->status_is(204, 'changed password back to original'); $t->post_ok('/login', json => { email => $ro_user->email, password => '123' }) ->status_is(200, 'logged in using original password'); - $t->get_ok('/user/me/settings') + $t->get_ok('/user/me/settings', { Authorization => 'Bearer '.$t->tx->res->json->{jwt_token} }) ->status_is(200, 'original password works again'); # reset db password entry to '' so we don't have to remember our password string @@ -393,7 +413,7 @@ subtest 'Log out' => sub { }; subtest 'JWT authentication' => sub { - $t->authenticate(email => $ro_user->email, bailout => 0) + $t->post_ok('/login', json => { email => $ro_user->email, password => '..' }) ->status_is(200) ->header_exists('Last-Modified') ->header_exists('Expires') @@ -402,11 +422,11 @@ subtest 'JWT authentication' => sub { my $jwt_token = $t->tx->res->json->{jwt_token}; - $t->reset_session; # force JWT to be used to authenticate + is($t->ua->cookie_jar->all->[0]->expires, 1, 'session cookie is expired'); + $t->get_ok('/workspace', { Authorization => 'Bearer '.$jwt_token }) ->status_is(200, 'user can provide Authentication header with full JWT to authenticate'); - $t->reset_session; # we're going to be cheeky here and hack the JWT to doctor it... # this only works because we have access to the symmetric secret embedded in the app. my $jwt_claims = Mojo::JWT->new(secret => $t->app->secrets->[0])->decode($jwt_token); @@ -466,7 +486,10 @@ subtest 'JWT authentication' => sub { $t->post_ok('/refresh_token', { Authorization => "Bearer $new_jwt_token" }) ->status_is(401, 'Cannot use after user revocation'); - $t->authenticate(email => $ro_user->email, bailout => 0); + $t->post_ok('/login', json => { email => $ro_user->email, password => '..' }) + ->status_is(200) + ->json_schema_is('Login'); + my $jwt_token_2 = $t->tx->res->json->{jwt_token}; $t->post_ok('/user/me/revoke', { Authorization => "Bearer $jwt_token_2" }) ->status_is(204, 'Revoke tokens for self') @@ -653,11 +676,12 @@ subtest 'modify another user' => sub { ->json_is($new_user_data); my $t2 = Test::Conch->new(pg => $t->pg); - $t2->post_ok('/login', json => { email => 'untrusted@conch.joyent.us', password => '123' }) + $t2->post_ok('/login', json => { email => 'untrusted@conch.joyent.us', password => '123', set_session => JSON::PP::true }) ->status_is(200, 'new user can log in'); my $jwt_token = $t2->tx->res->json->{jwt_token}; - $t2->get_ok('/me')->status_is(204); + $t2->get_ok('/me', { Authorization => 'Bearer '.$jwt_token }) + ->status_is(204); $t2->post_ok('/user/me/token', json => { name => 'my api token' }) ->header_exists('Last-Modified') @@ -703,7 +727,7 @@ subtest 'modify another user' => sub { $t2->get_ok('/me', { Authorization => "Bearer $api_token" }) ->status_is(401, 'new user cannot authenticate with the api token after api tokens are revoked'); - $t2->post_ok('/login', json => { email => 'untrusted@conch.joyent.us', password => '123' }) + $t2->post_ok('/login', json => { email => 'untrusted@conch.joyent.us', password => '123', set_session => JSON::PP::true }) ->status_is(200, 'new user can still log in again'); $jwt_token = $t2->tx->res->json->{jwt_token}; @@ -782,7 +806,7 @@ subtest 'modify another user' => sub { ->status_is(401) ->log_debug_is('password validation for untrusted@conch.joyent.us failed'); - $t2->post_ok('/login', json => { email => 'untrusted@conch.joyent.us', password => $insecure_password }) + $t2->post_ok('/login', json => { email => 'untrusted@conch.joyent.us', password => $insecure_password, set_session => JSON::PP::true }) ->status_is(200) ->log_info_is('user UNTRUSTED (untrusted@conch.joyent.us) logging in with one-time insecure password') ->location_is('/user/me/password'); @@ -811,7 +835,7 @@ subtest 'modify another user' => sub { my $secure_password = $_new_password; is($secure_password, 'a more secure password', 'provided password was saved to the db'); - $t2->post_ok('/login', json => { email => 'untrusted@conch.joyent.us', password => $secure_password }) + $t2->post_ok('/login', json => { email => 'untrusted@conch.joyent.us', password => $secure_password, set_session => JSON::PP::true }) ->status_is(200) ->log_info_is('user UNTRUSTED (untrusted@conch.joyent.us) logged in') ->json_has('/jwt_token') diff --git a/t/schema.t b/t/schema.t index 11e73210c..87500bf58 100644 --- a/t/schema.t +++ b/t/schema.t @@ -340,6 +340,7 @@ $t->get_ok('/schema/request/Login') user_id => { '$ref' => '/definitions/uuid' }, email => { '$ref' => '/definitions/email_address' }, password => { '$ref' => '/definitions/non_empty_string' }, + set_session => { type => 'boolean', default => JSON::PP::false }, }, definitions => { non_empty_string => { type => 'string', minLength => 1 }, From 09209e355511a3f8ef3ee1fcb029b0cd96816edc Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Wed, 15 Jan 2020 16:14:49 -0800 Subject: [PATCH 16/18] fix checking of refuse_session_auth this can be set at any time, and independently of force_password_change --- lib/Conch/Controller/Login.pm | 36 +++++++++++++++++------------------ lib/Conch/Controller/User.pm | 1 + t/integration/users.t | 25 ++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/lib/Conch/Controller/Login.pm b/lib/Conch/Controller/Login.pm index 50a2bd9a3..c79a5224b 100644 --- a/lib/Conch/Controller/Login.pm +++ b/lib/Conch/Controller/Login.pm @@ -118,26 +118,26 @@ sub authenticate ($c) { # api tokens are exempt from this check if ((not $session_token or $session_token->is_login) - and $user->refuse_session_auth) { - if ($user->force_password_change) { - if ($c->req->url ne '/user/me/password') { - $c->log->debug('attempt to authenticate before changing insecure password'); - - # ensure session and all login JWTs expire in no more than 10 minutes - $c->_update_session($c->session('user'), time + 10 * 60); - $user->user_session_tokens->login_only - ->update({ expires => \'least(expires, now() + interval \'10 minutes\')' }) if $session_token; - - $c->res->headers->location($c->url_for('/user/me/password')); - return $c->status(401); - } - } - else { - $c->log->debug('user\'s tokens were revoked - they must /login again'); - return $c->status(401); - } + and $user->force_password_change + and $c->req->url ne '/user/me/password' + ) { + $c->log->debug('attempt to authenticate before changing insecure password'); + + # ensure session and all login JWTs expire in no more than 10 minutes + $c->_update_session($c->session('user'), time + 10 * 60); + $user->user_session_tokens->login_only + ->update({ expires => \'least(expires, now() + interval \'10 minutes\')' }) if $session_token; + + $c->res->headers->location($c->url_for('/user/me/password')); + return $c->status(401); } + if (not $session_token and $user->refuse_session_auth) { + $c->log->debug('user attempting to authenticate with session, but refuse_session_auth is set'); + return $c->status(401); + } + + # the gauntlet has been successfully run! $c->stash('user_id', $user_id); $c->stash('user', $user); return 1; diff --git a/lib/Conch/Controller/User.pm b/lib/Conch/Controller/User.pm index dd545d9f7..259df11a0 100644 --- a/lib/Conch/Controller/User.pm +++ b/lib/Conch/Controller/User.pm @@ -487,6 +487,7 @@ sub create ($c) { $input->{is_admin} = ($input->{is_admin} ? 1 : 0); # password will be hashed in constructor + # FIXME: should set force_password_change - see GH#975 my $user = $c->db_user_accounts->create($input); $c->log->info('created user: '.$user->name.', email: '.$user->email.', id: '.$user->id); diff --git a/t/integration/users.t b/t/integration/users.t index 9f95af56b..ef93d3394 100644 --- a/t/integration/users.t +++ b/t/integration/users.t @@ -298,6 +298,17 @@ subtest 'User' => sub { 'session expires approximately 1 day in the future', ); + $t->get_ok('/user/me') + ->status_is(200, 'can authenticate with the session again') + ->json_is('/id', $ro_user->id); + + $ro_user->discard_changes; + $ro_user->update({ refuse_session_auth => 1 }); + + $t->get_ok('/user/me') + ->status_is(401) + ->log_debug_is('user attempting to authenticate with session, but refuse_session_auth is set'); + { my $t2 = Test::Conch->new(pg => $t->pg); $t2->get_ok('/user/me', { Authorization => 'Bearer '.$login_token[1] }) @@ -305,6 +316,7 @@ subtest 'User' => sub { ->json_schema_is('UserDetailed') ->json_cmp_deeply({ $user_detailed->%*, + refuse_session_auth => bool(1), workspaces => [ map +{ $_->%*, parent_workspace_id => undef }, $user_detailed->{workspaces}->@* ], last_login => re(qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3,9}Z$/), last_seen => re(qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3,9}Z$/), @@ -318,18 +330,27 @@ subtest 'User' => sub { ->json_schema_is('UserDetailed') ->json_cmp_deeply({ $user_detailed->%*, + refuse_session_auth => bool(1), workspaces => [ map +{ $_->%*, parent_workspace_id => undef }, $user_detailed->{workspaces}->@* ], last_login => re(qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3,9}Z$/), last_seen => re(qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3,9}Z$/), }); } - $t->post_ok('/user/me/token', json => { name => 'an api token' }) + $ro_user->discard_changes; + $ro_user->update({ refuse_session_auth => 0 }); + + $t->post_ok('/login', json => { email => $ro_user->email, password => '123', set_session => JSON::PP::true }) + ->status_is(200); + + is($ro_user->related_resultset('user_session_tokens')->count, 2, 'got second login token again'); + + $t->post_ok('/user/me/token', { Authorization => 'Bearer '.$login_token[0] }, json => { name => 'an api token' }) ->status_is(201) ->location_is('/user/me/token/an api token'); my $api_token = $t->tx->res->json->{token}; - $t->post_ok('/user/me/password', json => { password => 'øƕḩẳȋ' }) + $t->post_ok('/user/me/password', { Authorization => 'Bearer '.$login_token[0] }, json => { password => 'øƕḩẳȋ' }) ->status_is(204, 'changed password') ->email_not_sent; From 1a83f6c14cdf9546bc504bfc5b589bce0a20394c Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Thu, 16 Jan 2020 16:07:17 -0800 Subject: [PATCH 17/18] rename persistent session variable from "user" to "user_id" --- docs/modules/Test::Conch.md | 2 +- lib/Conch/Controller/Login.pm | 12 ++++++------ lib/Test/Conch.pm | 2 +- t/integration/users.t | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/modules/Test::Conch.md b/docs/modules/Test::Conch.md index c32860ca6..8755088c7 100644 --- a/docs/modules/Test::Conch.md +++ b/docs/modules/Test::Conch.md @@ -177,7 +177,7 @@ Authenticates a user in the current test instance. Uses default (superuser) cred provided. Optionally will bail out of **all** tests on failure. This will set 'user' in the session (`$t->ua->cookie_jar`, accessed internally via -`$c->session('user')`), so a token is not needed on subsequent requests. +`$c->session('user_id')`), so a token is not needed on subsequent requests. ## txn\_local diff --git a/lib/Conch/Controller/Login.pm b/lib/Conch/Controller/Login.pm index c79a5224b..f4ef56641 100644 --- a/lib/Conch/Controller/Login.pm +++ b/lib/Conch/Controller/Login.pm @@ -98,13 +98,13 @@ sub authenticate ($c) { $c->stash('token_id', $jwt_claims->{token_id}); } - if ($c->session('user')) { + if ($c->session('user_id')) { return $c->status(401, { error => 'user session is invalid' }) - if not is_uuid($c->session('user')) or ($user_id and $c->session('user') ne $user_id); + if not is_uuid($c->session('user_id')) or ($user_id and $c->session('user_id') ne $user_id); if (not $user_id) { - $user_id = $c->session('user'); - $c->log->debug('using session user='.$user_id); + $user_id = $c->session('user_id'); + $c->log->debug('using session user_id='.$user_id); } } @@ -124,7 +124,7 @@ sub authenticate ($c) { $c->log->debug('attempt to authenticate before changing insecure password'); # ensure session and all login JWTs expire in no more than 10 minutes - $c->_update_session($c->session('user'), time + 10 * 60); + $c->_update_session($c->session('user_id'), time + 10 * 60); $user->user_session_tokens->login_only ->update({ expires => \'least(expires, now() + interval \'10 minutes\')' }) if $session_token; @@ -291,7 +291,7 @@ sub _update_session ($c, $user_id = undef, $expires_epoch = 0) { $c->session('expires', 1); } else { - $c->session('user', $user_id); + $c->session('user_id', $user_id); $c->session('expires', $expires_epoch); } } diff --git a/lib/Test/Conch.pm b/lib/Test/Conch.pm index 9d711ce64..13f5dc57b 100644 --- a/lib/Test/Conch.pm +++ b/lib/Test/Conch.pm @@ -503,7 +503,7 @@ Authenticates a user in the current test instance. Uses default (superuser) cred provided. Optionally will bail out of B tests on failure. This will set 'user' in the session (C<< $t->ua->cookie_jar >>, accessed internally via -C<< $c->session('user') >>), so a token is not needed on subsequent requests. +C<< $c->session('user_id') >>), so a token is not needed on subsequent requests. =cut diff --git a/t/integration/users.t b/t/integration/users.t index ef93d3394..c9e1b5aac 100644 --- a/t/integration/users.t +++ b/t/integration/users.t @@ -865,7 +865,7 @@ subtest 'modify another user' => sub { $t2->get_ok('/me') ->status_is(204) - ->log_debug_is('using session user='.$new_user_id) + ->log_debug_is('using session user_id='.$new_user_id) ->log_debug_is('looking up user by id '.$new_user_id.': found UNTRUSTED (untrusted@conch.joyent.us)'); is($t2->tx->res->body, '', '...with no extra response messages'); From 182914d270e217dd205ffc00fc1a0def1b3e8117 Mon Sep 17 00:00:00 2001 From: Karen Etheridge Date: Thu, 16 Jan 2020 16:35:51 -0800 Subject: [PATCH 18/18] make session cookie even more secure - assert that a cookie ought not to be sent along with cross-site requests see https://tools.ietf.org/html/draft-west-first-party-cookies-07 - respect cookies with https requests only (although normally the proxy will block http requests anyway) --- lib/Conch.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Conch.pm b/lib/Conch.pm index b1b48a0ee..a6f9f5ce9 100644 --- a/lib/Conch.pm +++ b/lib/Conch.pm @@ -37,6 +37,8 @@ sub startup { $self->sessions->cookie_name('conch'); $self->sessions->default_expiration(86400); # 1 day + $self->sessions->samesite('Strict'); # do not send with cross-site requests + $self->sessions->secure(1) if $ENV{MOJO_MODE} eq 'production'; # https only $self->plugin('Config'); $self->secrets(delete $self->config->{secrets});