diff --git a/common/proto/auth.proto b/common/proto/auth.proto index f8c057cb..c3eda68b 100644 --- a/common/proto/auth.proto +++ b/common/proto/auth.proto @@ -34,6 +34,10 @@ service AuthService { rpc ListRolePermissions(ListRolePermissionsRequest) returns (RolePermissions); rpc ListGrantableObjects(ListGrantableObjectsRequest) returns (ListGrantableObjectsResponse); + // Support diagnostics. Requires read on diagnostics:internal-errors. + rpc ListInternalErrors(ListInternalErrorsRequest) returns (ListInternalErrorsResponse); + rpc GetInternalError(GetInternalErrorRequest) returns (InternalError); + // User administration. rpc AssignUserRole(AssignUserRoleRequest) returns (UserSummary); // Resets a lower-ranked user's password. @@ -184,7 +188,7 @@ message GrantableObject { string profile = 2; // Set only for a table-family root. string table = 3; - // One of global_data, global_journal, profile, journal, or table. + // One of global_data, global_journal, profile, journal, table, or diagnostics. string kind = 4; // Actions the caller may grant to target_role for this object. repeated string allowed_actions = 5; @@ -194,6 +198,50 @@ message ListGrantableObjectsResponse { repeated GrantableObject objects = 1; } +message InternalError { + string reference_id = 1; + string user_id = 2; + string username = 3; + string grpc_method = 4; + string detail = 5; + int64 occurred_at_unix_milliseconds = 6; +} + +message ListInternalErrorsRequest { + // Every non-empty text/UUID field is an optional filter. Username and method + // use case-insensitive substring matching. + string reference_id = 1; + string user_id = 2; + string username = 3; + string grpc_method = 4; + int64 occurred_from_unix_seconds = 5; + int64 occurred_to_unix_seconds = 6; + InternalErrorSort sort = 7; + // Unspecified sort always means newest first. For an explicit sort, false is + // ascending and true is descending. + bool descending = 8; + // Defaults to 100 and is capped at 500. + uint32 limit = 9; + uint64 offset = 10; +} + +enum InternalErrorSort { + INTERNAL_ERROR_SORT_UNSPECIFIED = 0; + INTERNAL_ERROR_SORT_OCCURRED_AT = 1; + INTERNAL_ERROR_SORT_REFERENCE_ID = 2; + INTERNAL_ERROR_SORT_USERNAME = 3; + INTERNAL_ERROR_SORT_GRPC_METHOD = 4; +} + +message ListInternalErrorsResponse { + repeated InternalError errors = 1; + uint64 total_count = 2; +} + +message GetInternalErrorRequest { + string reference_id = 1; +} + message AssignUserRoleRequest { string username = 1; string role = 2; diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 02500878..95136937 100644 Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ diff --git a/common/src/proto/komp_ac.auth.rs b/common/src/proto/komp_ac.auth.rs index 372b82d1..7b4d94b0 100644 --- a/common/src/proto/komp_ac.auth.rs +++ b/common/src/proto/komp_ac.auth.rs @@ -235,7 +235,7 @@ pub struct GrantableObject { /// Set only for a table-family root. #[prost(string, tag = "3")] pub table: ::prost::alloc::string::String, - /// One of global_data, global_journal, profile, journal, or table. + /// One of global_data, global_journal, profile, journal, table, or diagnostics. #[prost(string, tag = "4")] pub kind: ::prost::alloc::string::String, /// Actions the caller may grant to target_role for this object. @@ -250,6 +250,65 @@ pub struct ListGrantableObjectsResponse { } #[derive(serde::Serialize, serde::Deserialize)] #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct InternalError { + #[prost(string, tag = "1")] + pub reference_id: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub user_id: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub username: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub grpc_method: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub detail: ::prost::alloc::string::String, + #[prost(int64, tag = "6")] + pub occurred_at_unix_milliseconds: i64, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ListInternalErrorsRequest { + /// Every non-empty text/UUID field is an optional filter. Username and method + /// use case-insensitive substring matching. + #[prost(string, tag = "1")] + pub reference_id: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub user_id: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub username: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub grpc_method: ::prost::alloc::string::String, + #[prost(int64, tag = "5")] + pub occurred_from_unix_seconds: i64, + #[prost(int64, tag = "6")] + pub occurred_to_unix_seconds: i64, + #[prost(enumeration = "InternalErrorSort", tag = "7")] + pub sort: i32, + /// Unspecified sort always means newest first. For an explicit sort, false is + /// ascending and true is descending. + #[prost(bool, tag = "8")] + pub descending: bool, + /// Defaults to 100 and is capped at 500. + #[prost(uint32, tag = "9")] + pub limit: u32, + #[prost(uint64, tag = "10")] + pub offset: u64, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListInternalErrorsResponse { + #[prost(message, repeated, tag = "1")] + pub errors: ::prost::alloc::vec::Vec, + #[prost(uint64, tag = "2")] + pub total_count: u64, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetInternalErrorRequest { + #[prost(string, tag = "1")] + pub reference_id: ::prost::alloc::string::String, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct AssignUserRoleRequest { #[prost(string, tag = "1")] pub username: ::prost::alloc::string::String, @@ -287,6 +346,42 @@ pub struct ListUsersResponse { #[prost(message, repeated, tag = "1")] pub users: ::prost::alloc::vec::Vec, } +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum InternalErrorSort { + Unspecified = 0, + OccurredAt = 1, + ReferenceId = 2, + Username = 3, + GrpcMethod = 4, +} +impl InternalErrorSort { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Unspecified => "INTERNAL_ERROR_SORT_UNSPECIFIED", + Self::OccurredAt => "INTERNAL_ERROR_SORT_OCCURRED_AT", + Self::ReferenceId => "INTERNAL_ERROR_SORT_REFERENCE_ID", + Self::Username => "INTERNAL_ERROR_SORT_USERNAME", + Self::GrpcMethod => "INTERNAL_ERROR_SORT_GRPC_METHOD", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "INTERNAL_ERROR_SORT_UNSPECIFIED" => Some(Self::Unspecified), + "INTERNAL_ERROR_SORT_OCCURRED_AT" => Some(Self::OccurredAt), + "INTERNAL_ERROR_SORT_REFERENCE_ID" => Some(Self::ReferenceId), + "INTERNAL_ERROR_SORT_USERNAME" => Some(Self::Username), + "INTERNAL_ERROR_SORT_GRPC_METHOD" => Some(Self::GrpcMethod), + _ => None, + } + } +} /// Generated client implementations. pub mod auth_service_client { #![allow( @@ -716,6 +811,54 @@ pub mod auth_service_client { ); self.inner.unary(req, path, codec).await } + /// Support diagnostics. Requires read on diagnostics:internal-errors. + pub async fn list_internal_errors( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.auth.AuthService/ListInternalErrors", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("komp_ac.auth.AuthService", "ListInternalErrors"), + ); + self.inner.unary(req, path, codec).await + } + pub async fn get_internal_error( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.auth.AuthService/GetInternalError", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("komp_ac.auth.AuthService", "GetInternalError")); + self.inner.unary(req, path, codec).await + } /// User administration. pub async fn assign_user_role( &mut self, @@ -886,6 +1029,18 @@ pub mod auth_service_server { tonic::Response, tonic::Status, >; + /// Support diagnostics. Requires read on diagnostics:internal-errors. + async fn list_internal_errors( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn get_internal_error( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; /// User administration. async fn assign_user_role( &self, @@ -1614,6 +1769,98 @@ pub mod auth_service_server { }; Box::pin(fut) } + "/komp_ac.auth.AuthService/ListInternalErrors" => { + #[allow(non_camel_case_types)] + struct ListInternalErrorsSvc(pub Arc); + impl< + T: AuthService, + > tonic::server::UnaryService + for ListInternalErrorsSvc { + type Response = super::ListInternalErrorsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_internal_errors(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListInternalErrorsSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/komp_ac.auth.AuthService/GetInternalError" => { + #[allow(non_camel_case_types)] + struct GetInternalErrorSvc(pub Arc); + impl< + T: AuthService, + > tonic::server::UnaryService + for GetInternalErrorSvc { + type Response = super::InternalError; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_internal_error(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetInternalErrorSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/komp_ac.auth.AuthService/AssignUserRole" => { #[allow(non_camel_case_types)] struct AssignUserRoleSvc(pub Arc); diff --git a/komp-app/src/auth.rs b/komp-app/src/auth.rs index db6eaa44..6f533dab 100644 --- a/komp-app/src/auth.rs +++ b/komp-app/src/auth.rs @@ -1,8 +1,9 @@ use anyhow::{Context, Result}; use common::proto::komp_ac::auth::{ AddRoleRequest, AssignUserRoleRequest, AuthResponse, AuthorizationSnapshot, - ChangePasswordRequest, GetAuthorizationRequest, GrantPermissionRequest, - GrantableObject, ListGrantableObjectsRequest, ListRolePermissionsRequest, ListRolesRequest, + ChangePasswordRequest, GetAuthorizationRequest, GetInternalErrorRequest, GrantPermissionRequest, + GrantableObject, InternalError, ListGrantableObjectsRequest, ListInternalErrorsRequest, + ListInternalErrorsResponse, ListRolePermissionsRequest, ListRolesRequest, ListUsersRequest, LoginRequest, LoginResponse, LogoutRequest, PasswordOperationResponse, RegisterRequest, RemoveRoleRequest, ResetUserPasswordRequest, RevokePermissionRequest, RevokeUserSessionsRequest, Role, RolePermissions, UserSummary, @@ -245,6 +246,35 @@ impl AuthClient { .objects) } + pub async fn list_internal_errors( + &mut self, + token: &str, + request: ListInternalErrorsRequest, + ) -> Result { + Ok(self + .client + .list_internal_errors(authenticated_request(Some(token), request)?) + .await + .context("gRPC ListInternalErrors call failed")? + .into_inner()) + } + + pub async fn get_internal_error( + &mut self, + token: &str, + reference_id: String, + ) -> Result { + Ok(self + .client + .get_internal_error(authenticated_request( + Some(token), + GetInternalErrorRequest { reference_id }, + )?) + .await + .context("gRPC GetInternalError call failed")? + .into_inner()) + } + pub async fn assign_user_role( &mut self, token: &str, diff --git a/server b/server index f43dc625..81a105b9 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit f43dc62578245aabad13de2b5336f178d1b5f750 +Subproject commit 81a105b98d6486ec88fc52dc3ee721496da4d1df