aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2019-01-07 18:08:09 -0800
committerBryan Newbold <bnewbold@robocracy.org>2019-01-07 18:08:09 -0800
commit35f3f55aac364373ba16191abdb3c0c585249245 (patch)
tree444c4bd10e38c3935e250f760426c9a03536f035
parent75c9831cf6e71b7a24e99e34cbe74845d1bfeed0 (diff)
downloadfatcat-35f3f55aac364373ba16191abdb3c0c585249245.tar.gz
fatcat-35f3f55aac364373ba16191abdb3c0c585249245.zip
add auth/check endpoint
-rw-r--r--fatcat-openapi2.yml26
-rw-r--r--rust/HACKING.md9
-rw-r--r--rust/fatcat-api-spec/README.md3
-rw-r--r--rust/fatcat-api-spec/api.yaml26
-rw-r--r--rust/fatcat-api-spec/api/swagger.yaml66
-rw-r--r--rust/fatcat-api-spec/examples/client.rs9
-rw-r--r--rust/fatcat-api-spec/examples/server_lib/server.rs8
-rw-r--r--rust/fatcat-api-spec/src/client.rs103
-rw-r--r--rust/fatcat-api-spec/src/lib.rs22
-rw-r--r--rust/fatcat-api-spec/src/mimetypes.rs20
-rw-r--r--rust/fatcat-api-spec/src/server.rs118
-rw-r--r--rust/src/api_wrappers.rs96
-rw-r--r--rust/src/auth.rs21
13 files changed, 480 insertions, 47 deletions
diff --git a/fatcat-openapi2.yml b/fatcat-openapi2.yml
index 501a1296..625a0143 100644
--- a/fatcat-openapi2.yml
+++ b/fatcat-openapi2.yml
@@ -2711,4 +2711,30 @@ paths:
schema:
$ref: "#/definitions/error_response"
<<: *AUTHRESPONSES
+ /auth/check:
+ get:
+ operationId: "auth_check"
+ tags: # TAGLINE
+ security:
+ # required admin privs
+ - Bearer: []
+ parameters:
+ - name: role
+ in: query
+ required: false
+ type: string
+ responses:
+ 200:
+ description: Success
+ schema:
+ $ref: "#/definitions/success"
+ 400:
+ description: Bad Request
+ schema:
+ $ref: "#/definitions/error_response"
+ 500:
+ description: Generic Error
+ schema:
+ $ref: "#/definitions/error_response"
+ <<: *AUTHRESPONSES
diff --git a/rust/HACKING.md b/rust/HACKING.md
index 9d161b87..b3a551fa 100644
--- a/rust/HACKING.md
+++ b/rust/HACKING.md
@@ -54,6 +54,15 @@ Debug SQL schema errors (if diesel commands fail):
## Direct API Interaction
+First setup an auth token and check that authentication is working
+
+ EDITOR_ID='aaaaaaaaaaaabkvkaaaaaaaaay'
+ AUTH_TOKEN=`./target/debug/fatcat-auth create-token $EDITOR_ID`
+ http get :9411/v0/auth/check "Authorization:Bearer $AUTH_TOKEN"
+ http get :9411/v0/auth/check?role=admin "Authorization:Bearer $AUTH_TOKEN"
+
+You'll need to add the `$AUTH_TOKEN` bit to all requests below.
+
Creating entities via API:
http --json post localhost:9411/v0/container name=asdf issn=1234-5678
diff --git a/rust/fatcat-api-spec/README.md b/rust/fatcat-api-spec/README.md
index f81f641a..f8a6e817 100644
--- a/rust/fatcat-api-spec/README.md
+++ b/rust/fatcat-api-spec/README.md
@@ -13,7 +13,7 @@ To see how to make this your own, look here:
[README](https://github.com/swagger-api/swagger-codegen/blob/master/README.md)
- API version: 0.1.0
-- Build date: 2019-01-04T05:57:03.701Z
+- Build date: 2019-01-08T01:49:55.777Z
This autogenerated project defines an API crate `fatcat` which contains:
* An `Api` trait defining the API in Rust.
@@ -79,6 +79,7 @@ cargo run --example client GetCreatorReleases
cargo run --example client GetCreatorRevision
cargo run --example client LookupCreator
cargo run --example client UpdateCreator
+cargo run --example client AuthCheck
cargo run --example client AuthOidc
cargo run --example client GetEditor
cargo run --example client GetEditorChangelog
diff --git a/rust/fatcat-api-spec/api.yaml b/rust/fatcat-api-spec/api.yaml
index 501a1296..625a0143 100644
--- a/rust/fatcat-api-spec/api.yaml
+++ b/rust/fatcat-api-spec/api.yaml
@@ -2711,4 +2711,30 @@ paths:
schema:
$ref: "#/definitions/error_response"
<<: *AUTHRESPONSES
+ /auth/check:
+ get:
+ operationId: "auth_check"
+ tags: # TAGLINE
+ security:
+ # required admin privs
+ - Bearer: []
+ parameters:
+ - name: role
+ in: query
+ required: false
+ type: string
+ responses:
+ 200:
+ description: Success
+ schema:
+ $ref: "#/definitions/success"
+ 400:
+ description: Bad Request
+ schema:
+ $ref: "#/definitions/error_response"
+ 500:
+ description: Generic Error
+ schema:
+ $ref: "#/definitions/error_response"
+ <<: *AUTHRESPONSES
diff --git a/rust/fatcat-api-spec/api/swagger.yaml b/rust/fatcat-api-spec/api/swagger.yaml
index a19d6ae1..9d4767c0 100644
--- a/rust/fatcat-api-spec/api/swagger.yaml
+++ b/rust/fatcat-api-spec/api/swagger.yaml
@@ -6659,6 +6659,72 @@ paths:
HttpMethod: "Post"
httpmethod: "post"
noClientExample: true
+ /auth/check:
+ get:
+ operationId: "auth_check"
+ parameters:
+ - name: "role"
+ in: "query"
+ required: false
+ type: "string"
+ formatString: "{:?}"
+ example: "Some(\"role_example\".to_string())"
+ responses:
+ 200:
+ description: "Success"
+ schema:
+ $ref: "#/definitions/success"
+ x-responseId: "Success"
+ x-uppercaseResponseId: "SUCCESS"
+ uppercase_operation_id: "AUTH_CHECK"
+ uppercase_data_type: "SUCCESS"
+ producesJson: true
+ 400:
+ description: "Bad Request"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "BadRequest"
+ x-uppercaseResponseId: "BAD_REQUEST"
+ uppercase_operation_id: "AUTH_CHECK"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ 401:
+ description: "Not Authorized"
+ schema:
+ $ref: "#/definitions/error_response"
+ headers:
+ WWW_Authenticate:
+ type: "string"
+ x-responseId: "NotAuthorized"
+ x-uppercaseResponseId: "NOT_AUTHORIZED"
+ uppercase_operation_id: "AUTH_CHECK"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ 403:
+ description: "Forbidden"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "Forbidden"
+ x-uppercaseResponseId: "FORBIDDEN"
+ uppercase_operation_id: "AUTH_CHECK"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ 500:
+ description: "Generic Error"
+ schema:
+ $ref: "#/definitions/error_response"
+ x-responseId: "GenericError"
+ x-uppercaseResponseId: "GENERIC_ERROR"
+ uppercase_operation_id: "AUTH_CHECK"
+ uppercase_data_type: "ERRORRESPONSE"
+ producesJson: true
+ security:
+ - Bearer: []
+ operation_id: "auth_check"
+ uppercase_operation_id: "AUTH_CHECK"
+ path: "/auth/check"
+ HttpMethod: "Get"
+ httpmethod: "get"
securityDefinitions:
Bearer:
type: "apiKey"
diff --git a/rust/fatcat-api-spec/examples/client.rs b/rust/fatcat-api-spec/examples/client.rs
index 4eed8ae4..5a43a33c 100644
--- a/rust/fatcat-api-spec/examples/client.rs
+++ b/rust/fatcat-api-spec/examples/client.rs
@@ -12,8 +12,8 @@ extern crate uuid;
use clap::{App, Arg};
#[allow(unused_imports)]
use fatcat::{
- AcceptEditgroupResponse, ApiError, ApiNoContext, AuthOidcResponse, ContextWrapperExt, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse,
- CreateEditgroupResponse, CreateFileBatchResponse, CreateFileResponse, CreateFilesetBatchResponse, CreateFilesetResponse, CreateReleaseBatchResponse, CreateReleaseResponse,
+ AcceptEditgroupResponse, ApiError, ApiNoContext, AuthCheckResponse, AuthOidcResponse, ContextWrapperExt, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse,
+ CreateCreatorResponse, CreateEditgroupResponse, CreateFileBatchResponse, CreateFileResponse, CreateFilesetBatchResponse, CreateFilesetResponse, CreateReleaseBatchResponse, CreateReleaseResponse,
CreateWebcaptureBatchResponse, CreateWebcaptureResponse, CreateWorkBatchResponse, CreateWorkResponse, DeleteContainerEditResponse, DeleteContainerResponse, DeleteCreatorEditResponse,
DeleteCreatorResponse, DeleteFileEditResponse, DeleteFileResponse, DeleteFilesetEditResponse, DeleteFilesetResponse, DeleteReleaseEditResponse, DeleteReleaseResponse,
DeleteWebcaptureEditResponse, DeleteWebcaptureResponse, DeleteWorkEditResponse, DeleteWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerEditResponse,
@@ -54,6 +54,7 @@ fn main() {
"GetCreatorReleases",
"GetCreatorRevision",
"LookupCreator",
+ "AuthCheck",
"GetEditor",
"GetEditorChangelog",
"AcceptEditgroup",
@@ -272,6 +273,10 @@ fn main() {
// let result = client.update_creator("ident_example".to_string(), ???, Some("editgroup_id_example".to_string())).wait();
// println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
// },
+ Some("AuthCheck") => {
+ let result = client.auth_check(Some("role_example".to_string())).wait();
+ println!("{:?} (X-Span-ID: {:?})", result, client.context().x_span_id.clone().unwrap_or(String::from("<none>")));
+ }
// Disabled because there's no example.
// Some("AuthOidc") => {
diff --git a/rust/fatcat-api-spec/examples/server_lib/server.rs b/rust/fatcat-api-spec/examples/server_lib/server.rs
index 5e86a10e..73917351 100644
--- a/rust/fatcat-api-spec/examples/server_lib/server.rs
+++ b/rust/fatcat-api-spec/examples/server_lib/server.rs
@@ -11,7 +11,7 @@ use swagger;
use fatcat::models;
use fatcat::{
- AcceptEditgroupResponse, Api, ApiError, AuthOidcResponse, Context, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse,
+ AcceptEditgroupResponse, Api, ApiError, AuthCheckResponse, AuthOidcResponse, Context, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse,
CreateEditgroupResponse, CreateFileBatchResponse, CreateFileResponse, CreateFilesetBatchResponse, CreateFilesetResponse, CreateReleaseBatchResponse, CreateReleaseResponse,
CreateWebcaptureBatchResponse, CreateWebcaptureResponse, CreateWorkBatchResponse, CreateWorkResponse, DeleteContainerEditResponse, DeleteContainerResponse, DeleteCreatorEditResponse,
DeleteCreatorResponse, DeleteFileEditResponse, DeleteFileResponse, DeleteFilesetEditResponse, DeleteFilesetResponse, DeleteReleaseEditResponse, DeleteReleaseResponse,
@@ -297,6 +297,12 @@ impl Api for Server {
Box::new(futures::failed("Generic failure".into()))
}
+ fn auth_check(&self, role: Option<String>, context: &Context) -> Box<Future<Item = AuthCheckResponse, Error = ApiError> + Send> {
+ let context = context.clone();
+ println!("auth_check({:?}) - X-Span-ID: {:?}", role, context.x_span_id.unwrap_or(String::from("<none>")).clone());
+ Box::new(futures::failed("Generic failure".into()))
+ }
+
fn auth_oidc(&self, oidc_params: models::AuthOidc, context: &Context) -> Box<Future<Item = AuthOidcResponse, Error = ApiError> + Send> {
let context = context.clone();
println!("auth_oidc({:?}) - X-Span-ID: {:?}", oidc_params, context.x_span_id.unwrap_or(String::from("<none>")).clone());
diff --git a/rust/fatcat-api-spec/src/client.rs b/rust/fatcat-api-spec/src/client.rs
index 470a5350..7f364eb4 100644
--- a/rust/fatcat-api-spec/src/client.rs
+++ b/rust/fatcat-api-spec/src/client.rs
@@ -35,18 +35,19 @@ use swagger::{ApiError, Context, XSpanId};
use models;
use {
- AcceptEditgroupResponse, Api, AuthOidcResponse, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse,
- CreateFileBatchResponse, CreateFileResponse, CreateFilesetBatchResponse, CreateFilesetResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWebcaptureBatchResponse,
- CreateWebcaptureResponse, CreateWorkBatchResponse, CreateWorkResponse, DeleteContainerEditResponse, DeleteContainerResponse, DeleteCreatorEditResponse, DeleteCreatorResponse,
- DeleteFileEditResponse, DeleteFileResponse, DeleteFilesetEditResponse, DeleteFilesetResponse, DeleteReleaseEditResponse, DeleteReleaseResponse, DeleteWebcaptureEditResponse,
- DeleteWebcaptureResponse, DeleteWorkEditResponse, DeleteWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerEditResponse, GetContainerHistoryResponse,
- GetContainerRedirectsResponse, GetContainerResponse, GetContainerRevisionResponse, GetCreatorEditResponse, GetCreatorHistoryResponse, GetCreatorRedirectsResponse, GetCreatorReleasesResponse,
- GetCreatorResponse, GetCreatorRevisionResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileEditResponse, GetFileHistoryResponse, GetFileRedirectsResponse,
- GetFileResponse, GetFileRevisionResponse, GetFilesetEditResponse, GetFilesetHistoryResponse, GetFilesetRedirectsResponse, GetFilesetResponse, GetFilesetRevisionResponse, GetReleaseEditResponse,
- GetReleaseFilesResponse, GetReleaseFilesetsResponse, GetReleaseHistoryResponse, GetReleaseRedirectsResponse, GetReleaseResponse, GetReleaseRevisionResponse, GetReleaseWebcapturesResponse,
- GetWebcaptureEditResponse, GetWebcaptureHistoryResponse, GetWebcaptureRedirectsResponse, GetWebcaptureResponse, GetWebcaptureRevisionResponse, GetWorkEditResponse, GetWorkHistoryResponse,
- GetWorkRedirectsResponse, GetWorkReleasesResponse, GetWorkResponse, GetWorkRevisionResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse,
- UpdateContainerResponse, UpdateCreatorResponse, UpdateEditorResponse, UpdateFileResponse, UpdateFilesetResponse, UpdateReleaseResponse, UpdateWebcaptureResponse, UpdateWorkResponse,
+ AcceptEditgroupResponse, Api, AuthCheckResponse, AuthOidcResponse, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse,
+ CreateEditgroupResponse, CreateFileBatchResponse, CreateFileResponse, CreateFilesetBatchResponse, CreateFilesetResponse, CreateReleaseBatchResponse, CreateReleaseResponse,
+ CreateWebcaptureBatchResponse, CreateWebcaptureResponse, CreateWorkBatchResponse, CreateWorkResponse, DeleteContainerEditResponse, DeleteContainerResponse, DeleteCreatorEditResponse,
+ DeleteCreatorResponse, DeleteFileEditResponse, DeleteFileResponse, DeleteFilesetEditResponse, DeleteFilesetResponse, DeleteReleaseEditResponse, DeleteReleaseResponse,
+ DeleteWebcaptureEditResponse, DeleteWebcaptureResponse, DeleteWorkEditResponse, DeleteWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerEditResponse,
+ GetContainerHistoryResponse, GetContainerRedirectsResponse, GetContainerResponse, GetContainerRevisionResponse, GetCreatorEditResponse, GetCreatorHistoryResponse, GetCreatorRedirectsResponse,
+ GetCreatorReleasesResponse, GetCreatorResponse, GetCreatorRevisionResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileEditResponse, GetFileHistoryResponse,
+ GetFileRedirectsResponse, GetFileResponse, GetFileRevisionResponse, GetFilesetEditResponse, GetFilesetHistoryResponse, GetFilesetRedirectsResponse, GetFilesetResponse, GetFilesetRevisionResponse,
+ GetReleaseEditResponse, GetReleaseFilesResponse, GetReleaseFilesetsResponse, GetReleaseHistoryResponse, GetReleaseRedirectsResponse, GetReleaseResponse, GetReleaseRevisionResponse,
+ GetReleaseWebcapturesResponse, GetWebcaptureEditResponse, GetWebcaptureHistoryResponse, GetWebcaptureRedirectsResponse, GetWebcaptureResponse, GetWebcaptureRevisionResponse, GetWorkEditResponse,
+ GetWorkHistoryResponse, GetWorkRedirectsResponse, GetWorkReleasesResponse, GetWorkResponse, GetWorkRevisionResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse,
+ LookupReleaseResponse, UpdateContainerResponse, UpdateCreatorResponse, UpdateEditorResponse, UpdateFileResponse, UpdateFilesetResponse, UpdateReleaseResponse, UpdateWebcaptureResponse,
+ UpdateWorkResponse,
};
/// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes.
@@ -2030,6 +2031,84 @@ impl Api for Client {
Box::new(futures::done(result))
}
+ fn auth_check(&self, param_role: Option<String>, context: &Context) -> Box<Future<Item = AuthCheckResponse, Error = ApiError> + Send> {
+ // Query parameters
+ let query_role = param_role.map_or_else(String::new, |query| format!("role={role}&", role = query.to_string()));
+
+ let url = format!("{}/v0/auth/check?{role}", self.base_path, role = utf8_percent_encode(&query_role, QUERY_ENCODE_SET));
+
+ let hyper_client = (self.hyper_client)();
+ let request = hyper_client.request(hyper::method::Method::Get, &url);
+ let mut custom_headers = hyper::header::Headers::new();
+
+ context.x_span_id.as_ref().map(|header| custom_headers.set(XSpanId(header.clone())));
+
+ let request = request.headers(custom_headers);
+
+ // Helper function to provide a code block to use `?` in (to be replaced by the `catch` block when it exists).
+ fn parse_response(mut response: hyper::client::response::Response) -> Result<AuthCheckResponse, ApiError> {
+ match response.status.to_u16() {
+ 200 => {
+ let mut buf = String::new();
+ response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
+ let body = serde_json::from_str::<models::Success>(&buf)?;
+
+ Ok(AuthCheckResponse::Success(body))
+ }
+ 400 => {
+ let mut buf = String::new();
+ response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
+ let body = serde_json::from_str::<models::ErrorResponse>(&buf)?;
+
+ Ok(AuthCheckResponse::BadRequest(body))
+ }
+ 401 => {
+ let mut buf = String::new();
+ response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
+ let body = serde_json::from_str::<models::ErrorResponse>(&buf)?;
+ header! { (ResponseWwwAuthenticate, "WWW_Authenticate") => [String] }
+ let response_www_authenticate = response
+ .headers
+ .get::<ResponseWwwAuthenticate>()
+ .ok_or_else(|| "Required response header WWW_Authenticate for response 401 was not found.")?;
+
+ Ok(AuthCheckResponse::NotAuthorized {
+ body: body,
+ www_authenticate: response_www_authenticate.0.clone(),
+ })
+ }
+ 403 => {
+ let mut buf = String::new();
+ response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
+ let body = serde_json::from_str::<models::ErrorResponse>(&buf)?;
+
+ Ok(AuthCheckResponse::Forbidden(body))
+ }
+ 500 => {
+ let mut buf = String::new();
+ response.read_to_string(&mut buf).map_err(|e| ApiError(format!("Response was not valid UTF8: {}", e)))?;
+ let body = serde_json::from_str::<models::ErrorResponse>(&buf)?;
+
+ Ok(AuthCheckResponse::GenericError(body))
+ }
+ code => {
+ let mut buf = [0; 100];
+ let debug_body = match response.read(&mut buf) {
+ Ok(len) => match str::from_utf8(&buf[..len]) {
+ Ok(body) => Cow::from(body),
+ Err(_) => Cow::from(format!("<Body was not UTF8: {:?}>", &buf[..len].to_vec())),
+ },
+ Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
+ };
+ Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}", code, response.headers, debug_body)))
+ }
+ }
+ }
+
+ let result = request.send().map_err(|e| ApiError(format!("No response received: {}", e))).and_then(parse_response);
+ Box::new(futures::done(result))
+ }
+
fn auth_oidc(&self, param_oidc_params: models::AuthOidc, context: &Context) -> Box<Future<Item = AuthOidcResponse, Error = ApiError> + Send> {
let url = format!("{}/v0/auth/oidc", self.base_path);
diff --git a/rust/fatcat-api-spec/src/lib.rs b/rust/fatcat-api-spec/src/lib.rs
index 258b635b..17c74384 100644
--- a/rust/fatcat-api-spec/src/lib.rs
+++ b/rust/fatcat-api-spec/src/lib.rs
@@ -349,6 +349,20 @@ pub enum UpdateCreatorResponse {
}
#[derive(Debug, PartialEq)]
+pub enum AuthCheckResponse {
+ /// Success
+ Success(models::Success),
+ /// Bad Request
+ BadRequest(models::ErrorResponse),
+ /// Not Authorized
+ NotAuthorized { body: models::ErrorResponse, www_authenticate: String },
+ /// Forbidden
+ Forbidden(models::ErrorResponse),
+ /// Generic Error
+ GenericError(models::ErrorResponse),
+}
+
+#[derive(Debug, PartialEq)]
pub enum AuthOidcResponse {
/// Found
Found(models::AuthOidcResult),
@@ -1315,6 +1329,8 @@ pub trait Api {
fn update_creator(&self, ident: String, entity: models::CreatorEntity, editgroup_id: Option<String>, context: &Context) -> Box<Future<Item = UpdateCreatorResponse, Error = ApiError> + Send>;
+ fn auth_check(&self, role: Option<String>, context: &Context) -> Box<Future<Item = AuthCheckResponse, Error = ApiError> + Send>;
+
fn auth_oidc(&self, oidc_params: models::AuthOidc, context: &Context) -> Box<Future<Item = AuthOidcResponse, Error = ApiError> + Send>;
fn get_editor(&self, editor_id: String, context: &Context) -> Box<Future<Item = GetEditorResponse, Error = ApiError> + Send>;
@@ -1565,6 +1581,8 @@ pub trait ApiNoContext {
fn update_creator(&self, ident: String, entity: models::CreatorEntity, editgroup_id: Option<String>) -> Box<Future<Item = UpdateCreatorResponse, Error = ApiError> + Send>;
+ fn auth_check(&self, role: Option<String>) -> Box<Future<Item = AuthCheckResponse, Error = ApiError> + Send>;
+
fn auth_oidc(&self, oidc_params: models::AuthOidc) -> Box<Future<Item = AuthOidcResponse, Error = ApiError> + Send>;
fn get_editor(&self, editor_id: String) -> Box<Future<Item = GetEditorResponse, Error = ApiError> + Send>;
@@ -1852,6 +1870,10 @@ impl<'a, T: Api> ApiNoContext for ContextWrapper<'a, T> {
self.api().update_creator(ident, entity, editgroup_id, &self.context())
}
+ fn auth_check(&self, role: Option<String>) -> Box<Future<Item = AuthCheckResponse, Error = ApiError> + Send> {
+ self.api().auth_check(role, &self.context())
+ }
+
fn auth_oidc(&self, oidc_params: models::AuthOidc) -> Box<Future<Item = AuthOidcResponse, Error = ApiError> + Send> {
self.api().auth_oidc(oidc_params, &self.context())
}
diff --git a/rust/fatcat-api-spec/src/mimetypes.rs b/rust/fatcat-api-spec/src/mimetypes.rs
index cfdd357d..83add9e3 100644
--- a/rust/fatcat-api-spec/src/mimetypes.rs
+++ b/rust/fatcat-api-spec/src/mimetypes.rs
@@ -452,6 +452,26 @@ pub mod responses {
lazy_static! {
pub static ref UPDATE_CREATOR_GENERIC_ERROR: Mime = mime!(Application / Json);
}
+ /// Create Mime objects for the response content types for AuthCheck
+ lazy_static! {
+ pub static ref AUTH_CHECK_SUCCESS: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for AuthCheck
+ lazy_static! {
+ pub static ref AUTH_CHECK_BAD_REQUEST: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for AuthCheck
+ lazy_static! {
+ pub static ref AUTH_CHECK_NOT_AUTHORIZED: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for AuthCheck
+ lazy_static! {
+ pub static ref AUTH_CHECK_FORBIDDEN: Mime = mime!(Application / Json);
+ }
+ /// Create Mime objects for the response content types for AuthCheck
+ lazy_static! {
+ pub static ref AUTH_CHECK_GENERIC_ERROR: Mime = mime!(Application / Json);
+ }
/// Create Mime objects for the response content types for AuthOidc
lazy_static! {
pub static ref AUTH_OIDC_FOUND: Mime = mime!(Application / Json);
diff --git a/rust/fatcat-api-spec/src/server.rs b/rust/fatcat-api-spec/src/server.rs
index c0903676..d8fc7dc2 100644
--- a/rust/fatcat-api-spec/src/server.rs
+++ b/rust/fatcat-api-spec/src/server.rs
@@ -37,18 +37,19 @@ use swagger::{ApiError, Context, XSpanId};
#[allow(unused_imports)]
use models;
use {
- AcceptEditgroupResponse, Api, AuthOidcResponse, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse, CreateEditgroupResponse,
- CreateFileBatchResponse, CreateFileResponse, CreateFilesetBatchResponse, CreateFilesetResponse, CreateReleaseBatchResponse, CreateReleaseResponse, CreateWebcaptureBatchResponse,
- CreateWebcaptureResponse, CreateWorkBatchResponse, CreateWorkResponse, DeleteContainerEditResponse, DeleteContainerResponse, DeleteCreatorEditResponse, DeleteCreatorResponse,
- DeleteFileEditResponse, DeleteFileResponse, DeleteFilesetEditResponse, DeleteFilesetResponse, DeleteReleaseEditResponse, DeleteReleaseResponse, DeleteWebcaptureEditResponse,
- DeleteWebcaptureResponse, DeleteWorkEditResponse, DeleteWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerEditResponse, GetContainerHistoryResponse,
- GetContainerRedirectsResponse, GetContainerResponse, GetContainerRevisionResponse, GetCreatorEditResponse, GetCreatorHistoryResponse, GetCreatorRedirectsResponse, GetCreatorReleasesResponse,
- GetCreatorResponse, GetCreatorRevisionResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileEditResponse, GetFileHistoryResponse, GetFileRedirectsResponse,
- GetFileResponse, GetFileRevisionResponse, GetFilesetEditResponse, GetFilesetHistoryResponse, GetFilesetRedirectsResponse, GetFilesetResponse, GetFilesetRevisionResponse, GetReleaseEditResponse,
- GetReleaseFilesResponse, GetReleaseFilesetsResponse, GetReleaseHistoryResponse, GetReleaseRedirectsResponse, GetReleaseResponse, GetReleaseRevisionResponse, GetReleaseWebcapturesResponse,
- GetWebcaptureEditResponse, GetWebcaptureHistoryResponse, GetWebcaptureRedirectsResponse, GetWebcaptureResponse, GetWebcaptureRevisionResponse, GetWorkEditResponse, GetWorkHistoryResponse,
- GetWorkRedirectsResponse, GetWorkReleasesResponse, GetWorkResponse, GetWorkRevisionResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse, LookupReleaseResponse,
- UpdateContainerResponse, UpdateCreatorResponse, UpdateEditorResponse, UpdateFileResponse, UpdateFilesetResponse, UpdateReleaseResponse, UpdateWebcaptureResponse, UpdateWorkResponse,
+ AcceptEditgroupResponse, Api, AuthCheckResponse, AuthOidcResponse, CreateContainerBatchResponse, CreateContainerResponse, CreateCreatorBatchResponse, CreateCreatorResponse,
+ CreateEditgroupResponse, CreateFileBatchResponse, CreateFileResponse, CreateFilesetBatchResponse, CreateFilesetResponse, CreateReleaseBatchResponse, CreateReleaseResponse,
+ CreateWebcaptureBatchResponse, CreateWebcaptureResponse, CreateWorkBatchResponse, CreateWorkResponse, DeleteContainerEditResponse, DeleteContainerResponse, DeleteCreatorEditResponse,
+ DeleteCreatorResponse, DeleteFileEditResponse, DeleteFileResponse, DeleteFilesetEditResponse, DeleteFilesetResponse, DeleteReleaseEditResponse, DeleteReleaseResponse,
+ DeleteWebcaptureEditResponse, DeleteWebcaptureResponse, DeleteWorkEditResponse, DeleteWorkResponse, GetChangelogEntryResponse, GetChangelogResponse, GetContainerEditResponse,
+ GetContainerHistoryResponse, GetContainerRedirectsResponse, GetContainerResponse, GetContainerRevisionResponse, GetCreatorEditResponse, GetCreatorHistoryResponse, GetCreatorRedirectsResponse,
+ GetCreatorReleasesResponse, GetCreatorResponse, GetCreatorRevisionResponse, GetEditgroupResponse, GetEditorChangelogResponse, GetEditorResponse, GetFileEditResponse, GetFileHistoryResponse,
+ GetFileRedirectsResponse, GetFileResponse, GetFileRevisionResponse, GetFilesetEditResponse, GetFilesetHistoryResponse, GetFilesetRedirectsResponse, GetFilesetResponse, GetFilesetRevisionResponse,
+ GetReleaseEditResponse, GetReleaseFilesResponse, GetReleaseFilesetsResponse, GetReleaseHistoryResponse, GetReleaseRedirectsResponse, GetReleaseResponse, GetReleaseRevisionResponse,
+ GetReleaseWebcapturesResponse, GetWebcaptureEditResponse, GetWebcaptureHistoryResponse, GetWebcaptureRedirectsResponse, GetWebcaptureResponse, GetWebcaptureRevisionResponse, GetWorkEditResponse,
+ GetWorkHistoryResponse, GetWorkRedirectsResponse, GetWorkReleasesResponse, GetWorkResponse, GetWorkRevisionResponse, LookupContainerResponse, LookupCreatorResponse, LookupFileResponse,
+ LookupReleaseResponse, UpdateContainerResponse, UpdateCreatorResponse, UpdateEditorResponse, UpdateFileResponse, UpdateFilesetResponse, UpdateReleaseResponse, UpdateWebcaptureResponse,
+ UpdateWorkResponse,
};
header! { (Warning, "Warning") => [String] }
@@ -2606,6 +2607,99 @@ where
);
let api_clone = api.clone();
+ router.get(
+ "/v0/auth/check",
+ move |req: &mut Request| {
+ let mut context = Context::default();
+
+ // Helper function to provide a code block to use `?` in (to be replaced by the `catch` block when it exists).
+ fn handle_request<T>(req: &mut Request, api: &T, context: &mut Context) -> Result<Response, Response>
+ where
+ T: Api,
+ {
+ context.x_span_id = Some(req.headers.get::<XSpanId>().map(XSpanId::to_string).unwrap_or_else(|| self::uuid::Uuid::new_v4().to_string()));
+ context.auth_data = req.extensions.remove::<AuthData>();
+ context.authorization = req.extensions.remove::<Authorization>();
+
+ let authorization = context.authorization.as_ref().ok_or_else(|| Response::with((status::Forbidden, "Unauthenticated".to_string())))?;
+
+ // Query parameters (note that non-required or collection query parameters will ignore garbage values, rather than causing a 400 response)
+ let query_params = req.get::<UrlEncodedQuery>().unwrap_or_default();
+ let param_role = query_params.get("role").and_then(|list| list.first()).and_then(|x| x.parse::<String>().ok());
+
+ match api.auth_check(param_role, context).wait() {
+ Ok(rsp) => match rsp {
+ AuthCheckResponse::Success(body) => {
+ let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize");
+
+ let mut response = Response::with((status::Status::from_u16(200), body_string));
+ response.headers.set(ContentType(mimetypes::responses::AUTH_CHECK_SUCCESS.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ AuthCheckResponse::BadRequest(body) => {
+ let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize");
+
+ let mut response = Response::with((status::Status::from_u16(400), body_string));
+ response.headers.set(ContentType(mimetypes::responses::AUTH_CHECK_BAD_REQUEST.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ AuthCheckResponse::NotAuthorized { body, www_authenticate } => {
+ let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize");
+
+ let mut response = Response::with((status::Status::from_u16(401), body_string));
+ header! { (ResponseWwwAuthenticate, "WWW_Authenticate") => [String] }
+ response.headers.set(ResponseWwwAuthenticate(www_authenticate));
+
+ response.headers.set(ContentType(mimetypes::responses::AUTH_CHECK_NOT_AUTHORIZED.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ AuthCheckResponse::Forbidden(body) => {
+ let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize");
+
+ let mut response = Response::with((status::Status::from_u16(403), body_string));
+ response.headers.set(ContentType(mimetypes::responses::AUTH_CHECK_FORBIDDEN.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ AuthCheckResponse::GenericError(body) => {
+ let body_string = serde_json::to_string(&body).expect("impossible to fail to serialize");
+
+ let mut response = Response::with((status::Status::from_u16(500), body_string));
+ response.headers.set(ContentType(mimetypes::responses::AUTH_CHECK_GENERIC_ERROR.clone()));
+
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+
+ Ok(response)
+ }
+ },
+ Err(_) => {
+ // Application code returned an error. This should not happen, as the implementation should
+ // return a valid response.
+ Err(Response::with((status::InternalServerError, "An internal error occurred".to_string())))
+ }
+ }
+ }
+
+ handle_request(req, &api_clone, &mut context).or_else(|mut response| {
+ context.x_span_id.as_ref().map(|header| response.headers.set(XSpanId(header.clone())));
+ Ok(response)
+ })
+ },
+ "AuthCheck",
+ );
+
+ let api_clone = api.clone();
router.post(
"/v0/auth/oidc",
move |req: &mut Request| {
diff --git a/rust/src/api_wrappers.rs b/rust/src/api_wrappers.rs
index 614a0007..818a41c2 100644
--- a/rust/src/api_wrappers.rs
+++ b/rust/src/api_wrappers.rs
@@ -404,8 +404,7 @@ macro_rules! wrap_entity_handlers {
$model::db_delete_edit(&conn, edit_id)
}) {
Ok(()) =>
- $delete_edit_resp::DeletedEdit(Success { message: format!("Successfully deleted work-in-progress {} edit: {}", stringify!($model), edit_id) } ),
- Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) =>
+ $delete_edit_resp::DeletedEdit(Success { message: format!("Successfully deleted work-in-progress {} edit: {}", stringify!($model), edit_id) } ), Err(Error(ErrorKind::Diesel(::diesel::result::Error::NotFound), _)) =>
$delete_edit_resp::NotFound(ErrorResponse { message: format!("No such {} edit: {}", stringify!($model), edit_id) }),
Err(Error(ErrorKind::Diesel(e), _)) =>
$delete_edit_resp::BadRequest(ErrorResponse { message: e.to_string() }),
@@ -918,9 +917,11 @@ impl Api for Server {
ErrorKind::OtherBadRequest("editor_id doesn't match".to_string()).into(),
);
}
- let auth_context = self
- .auth_confectionary
- .require_auth(&conn, &context.auth_data, Some("update_editor"))?;
+ let auth_context = self.auth_confectionary.require_auth(
+ &conn,
+ &context.auth_data,
+ Some("update_editor"),
+ )?;
let editor_id = FatCatId::from_str(&editor_id)?;
// DANGER! these permissions are for username updates only!
if editor_id == auth_context.editor_id {
@@ -986,9 +987,11 @@ impl Api for Server {
let conn = self.db_pool.get().expect("db_pool error");
let ret = match conn.transaction(|| {
let editgroup_id = FatCatId::from_str(&editgroup_id)?;
- let auth_context = self
- .auth_confectionary
- .require_auth(&conn, &context.auth_data, Some("accept_editgroup"))?;
+ let auth_context = self.auth_confectionary.require_auth(
+ &conn,
+ &context.auth_data,
+ Some("accept_editgroup"),
+ )?;
auth_context.require_role(FatcatRole::Admin)?;
// NOTE: this is currently redundant, but zero-cost
auth_context.require_editgroup(&conn, editgroup_id)?;
@@ -1058,9 +1061,11 @@ impl Api for Server {
) -> Box<Future<Item = CreateEditgroupResponse, Error = ApiError> + Send> {
let conn = self.db_pool.get().expect("db_pool error");
let ret = match conn.transaction(|| {
- let auth_context = self
- .auth_confectionary
- .require_auth(&conn, &context.auth_data, Some("create_editgroup"))?;
+ let auth_context = self.auth_confectionary.require_auth(
+ &conn,
+ &context.auth_data,
+ Some("create_editgroup"),
+ )?;
auth_context.require_role(FatcatRole::Editor)?;
let mut entity = entity.clone();
match entity.editor_id.clone() {
@@ -1149,9 +1154,11 @@ impl Api for Server {
) -> Box<Future<Item = AuthOidcResponse, Error = ApiError> + Send> {
let conn = self.db_pool.get().expect("db_pool error");
let ret = match conn.transaction(|| {
- let auth_context = self
- .auth_confectionary
- .require_auth(&conn, &context.auth_data, Some("auth_oidc"))?;
+ let auth_context = self.auth_confectionary.require_auth(
+ &conn,
+ &context.auth_data,
+ Some("auth_oidc"),
+ )?;
auth_context.require_role(FatcatRole::Superuser)?;
let (editor, created) = self.auth_oidc_handler(params, &conn)?;
// create an auth token; leave it to webface to attenuate to a given duration
@@ -1221,4 +1228,65 @@ impl Api for Server {
};
Box::new(futures::done(Ok(ret)))
}
+
+ fn auth_check(
+ &self,
+ role: Option<String>,
+ context: &Context,
+ ) -> Box<Future<Item = AuthCheckResponse, Error = ApiError> + Send> {
+ let conn = self.db_pool.get().expect("db_pool error");
+ let ret = match conn.transaction(|| {
+ let auth_context = self.auth_confectionary.require_auth(
+ &conn,
+ &context.auth_data,
+ Some("auth_check"),
+ )?;
+ if let Some(role) = role {
+ let role = match role.to_lowercase().as_ref() {
+ "superuser" => FatcatRole::Superuser,
+ "admin" => FatcatRole::Admin,
+ "editor" => FatcatRole::Editor,
+ "bot" => FatcatRole::Bot,
+ "human" => FatcatRole::Human,
+ "public" => FatcatRole::Public,
+ _ => bail!("unknown auth role: {}", role),
+ };
+ auth_context.require_role(role)?;
+ };
+ Ok(())
+ }) {
+ Ok(()) => AuthCheckResponse::Success(Success {
+ message: "auth check successful!".to_string() }),
+ Err(Error(ErrorKind::Diesel(e), _)) => AuthCheckResponse::BadRequest(ErrorResponse {
+ message: e.to_string(),
+ }),
+ Err(Error(ErrorKind::Uuid(e), _)) => AuthCheckResponse::BadRequest(ErrorResponse {
+ message: e.to_string(),
+ }),
+ Err(Error(ErrorKind::InvalidCredentials(e), _)) =>
+ // TODO: why can't I NotAuthorized here?
+ {
+ AuthCheckResponse::Forbidden(ErrorResponse {
+ message: e.to_string(),
+ })
+ },
+ Err(Error(ErrorKind::InsufficientPrivileges(e), _)) => {
+ AuthCheckResponse::Forbidden(ErrorResponse {
+ message: e.to_string(),
+ })
+ },
+ Err(Error(ErrorKind::OtherBadRequest(e), _)) => {
+ AuthCheckResponse::BadRequest(ErrorResponse {
+ message: e.to_string(),
+ })
+ },
+ Err(e) => {
+ error!("{}", e);
+ AuthCheckResponse::GenericError(ErrorResponse {
+ message: e.to_string(),
+ })
+ },
+ };
+ Box::new(futures::done(Ok(ret)))
+ }
}
diff --git a/rust/src/auth.rs b/rust/src/auth.rs
index 8894e33b..c20b9b71 100644
--- a/rust/src/auth.rs
+++ b/rust/src/auth.rs
@@ -56,9 +56,10 @@ impl AuthContext {
pub fn require_role(&self, role: FatcatRole) -> Result<()> {
match self.has_role(role) {
true => Ok(()),
- false => Err(ErrorKind::InsufficientPrivileges(
- format!("doesn't have required role: {:?}", role),
- )
+ false => Err(ErrorKind::InsufficientPrivileges(format!(
+ "doesn't have required role: {:?}",
+ role
+ ))
.into()),
}
}
@@ -230,7 +231,12 @@ impl AuthConfectionary {
Ok(BASE64.encode(&raw))
}
- pub fn parse_macaroon_token(&self, conn: &DbConn, s: &str, endpoint: Option<&str>) -> Result<EditorRow> {
+ pub fn parse_macaroon_token(
+ &self,
+ conn: &DbConn,
+ s: &str,
+ endpoint: Option<&str>,
+ ) -> Result<EditorRow> {
let raw = BASE64.decode(s.as_bytes())?;
let mac = match Macaroon::deserialize(&raw) {
Ok(m) => m,
@@ -371,7 +377,12 @@ impl AuthConfectionary {
}))
}
- pub fn require_auth(&self, conn: &DbConn, auth_data: &Option<AuthData>, endpoint: Option<&str>) -> Result<AuthContext> {
+ pub fn require_auth(
+ &self,
+ conn: &DbConn,
+ auth_data: &Option<AuthData>,
+ endpoint: Option<&str>,
+ ) -> Result<AuthContext> {
match self.parse_swagger(conn, auth_data, endpoint)? {
Some(auth) => Ok(auth),
None => Err(ErrorKind::InvalidCredentials("no token supplied".to_string()).into()),