mirror of
https://github.com/meilisearch/meilisearch.git
synced 2024-11-26 20:15:07 +08:00
Merge #1492
1492: auth tests r=irevoire a=MarinPostma add regression tests on route authentication. Co-authored-by: mpostma <postma.marin@protonmail.com>
This commit is contained in:
commit
751d1af2a6
@ -11,7 +11,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
.service(web::resource("/{dump_uid}/status").route(web::get().to(get_dump_status)));
|
.service(web::resource("/{dump_uid}/status").route(web::get().to(get_dump_status)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_dump(data: GuardedData<Private, Data>) -> Result<HttpResponse, ResponseError> {
|
pub async fn create_dump(data: GuardedData<Private, Data>) -> Result<HttpResponse, ResponseError> {
|
||||||
let res = data.create_dump().await?;
|
let res = data.create_dump().await?;
|
||||||
|
|
||||||
debug!("returns: {:?}", res);
|
debug!("returns: {:?}", res);
|
||||||
|
@ -49,7 +49,7 @@ fn guard_json(head: &actix_web::dev::RequestHead) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct DocumentParam {
|
pub struct DocumentParam {
|
||||||
index_uid: String,
|
index_uid: String,
|
||||||
document_id: String,
|
document_id: String,
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_document(
|
pub async fn get_document(
|
||||||
data: GuardedData<Public, Data>,
|
data: GuardedData<Public, Data>,
|
||||||
path: web::Path<DocumentParam>,
|
path: web::Path<DocumentParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -84,7 +84,7 @@ async fn get_document(
|
|||||||
Ok(HttpResponse::Ok().json(document))
|
Ok(HttpResponse::Ok().json(document))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_document(
|
pub async fn delete_document(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<DocumentParam>,
|
path: web::Path<DocumentParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -97,13 +97,13 @@ async fn delete_document(
|
|||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
struct BrowseQuery {
|
pub struct BrowseQuery {
|
||||||
offset: Option<usize>,
|
offset: Option<usize>,
|
||||||
limit: Option<usize>,
|
limit: Option<usize>,
|
||||||
attributes_to_retrieve: Option<String>,
|
attributes_to_retrieve: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_documents(
|
pub async fn get_all_documents(
|
||||||
data: GuardedData<Public, Data>,
|
data: GuardedData<Public, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
params: web::Query<BrowseQuery>,
|
params: web::Query<BrowseQuery>,
|
||||||
@ -134,13 +134,13 @@ async fn get_all_documents(
|
|||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
struct UpdateDocumentsQuery {
|
pub struct UpdateDocumentsQuery {
|
||||||
primary_key: Option<String>,
|
primary_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Route used when the payload type is "application/json"
|
/// Route used when the payload type is "application/json"
|
||||||
/// Used to add or replace documents
|
/// Used to add or replace documents
|
||||||
async fn add_documents(
|
pub async fn add_documents(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
params: web::Query<UpdateDocumentsQuery>,
|
params: web::Query<UpdateDocumentsQuery>,
|
||||||
@ -163,7 +163,7 @@ async fn add_documents(
|
|||||||
|
|
||||||
/// Route used when the payload type is "application/json"
|
/// Route used when the payload type is "application/json"
|
||||||
/// Used to add or replace documents
|
/// Used to add or replace documents
|
||||||
async fn update_documents(
|
pub async fn update_documents(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
params: web::Query<UpdateDocumentsQuery>,
|
params: web::Query<UpdateDocumentsQuery>,
|
||||||
@ -184,7 +184,7 @@ async fn update_documents(
|
|||||||
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update.id() })))
|
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update.id() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_documents(
|
pub async fn delete_documents(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
body: web::Json<Vec<Value>>,
|
body: web::Json<Vec<Value>>,
|
||||||
@ -204,7 +204,7 @@ async fn delete_documents(
|
|||||||
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn clear_all_documents(
|
pub async fn clear_all_documents(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
@ -8,10 +8,10 @@ use crate::extractors::authentication::{policies::*, GuardedData};
|
|||||||
use crate::routes::IndexParam;
|
use crate::routes::IndexParam;
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
|
|
||||||
mod documents;
|
pub mod documents;
|
||||||
mod search;
|
pub mod search;
|
||||||
mod settings;
|
pub mod settings;
|
||||||
mod updates;
|
pub mod updates;
|
||||||
|
|
||||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(
|
||||||
@ -35,7 +35,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_indexes(data: GuardedData<Private, Data>) -> Result<HttpResponse, ResponseError> {
|
pub async fn list_indexes(data: GuardedData<Private, Data>) -> Result<HttpResponse, ResponseError> {
|
||||||
let indexes = data.list_indexes().await?;
|
let indexes = data.list_indexes().await?;
|
||||||
debug!("returns: {:?}", indexes);
|
debug!("returns: {:?}", indexes);
|
||||||
Ok(HttpResponse::Ok().json(indexes))
|
Ok(HttpResponse::Ok().json(indexes))
|
||||||
@ -43,12 +43,12 @@ async fn list_indexes(data: GuardedData<Private, Data>) -> Result<HttpResponse,
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
struct IndexCreateRequest {
|
pub struct IndexCreateRequest {
|
||||||
uid: String,
|
uid: String,
|
||||||
primary_key: Option<String>,
|
primary_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_index(
|
pub async fn create_index(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
body: web::Json<IndexCreateRequest>,
|
body: web::Json<IndexCreateRequest>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -59,7 +59,7 @@ async fn create_index(
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
struct UpdateIndexRequest {
|
pub struct UpdateIndexRequest {
|
||||||
uid: Option<String>,
|
uid: Option<String>,
|
||||||
primary_key: Option<String>,
|
primary_key: Option<String>,
|
||||||
}
|
}
|
||||||
@ -74,7 +74,7 @@ pub struct UpdateIndexResponse {
|
|||||||
primary_key: Option<String>,
|
primary_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_index(
|
pub async fn get_index(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -83,7 +83,7 @@ async fn get_index(
|
|||||||
Ok(HttpResponse::Ok().json(meta))
|
Ok(HttpResponse::Ok().json(meta))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_index(
|
pub async fn update_index(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
body: web::Json<UpdateIndexRequest>,
|
body: web::Json<UpdateIndexRequest>,
|
||||||
@ -97,7 +97,7 @@ async fn update_index(
|
|||||||
Ok(HttpResponse::Ok().json(meta))
|
Ok(HttpResponse::Ok().json(meta))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_index(
|
pub async fn delete_index(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -105,7 +105,7 @@ async fn delete_index(
|
|||||||
Ok(HttpResponse::NoContent().finish())
|
Ok(HttpResponse::NoContent().finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_index_stats(
|
pub async fn get_index_stats(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
@ -77,7 +77,7 @@ impl From<SearchQueryGet> for SearchQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn search_with_url_query(
|
pub async fn search_with_url_query(
|
||||||
data: GuardedData<Public, Data>,
|
data: GuardedData<Public, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
params: web::Query<SearchQueryGet>,
|
params: web::Query<SearchQueryGet>,
|
||||||
@ -94,7 +94,7 @@ async fn search_with_url_query(
|
|||||||
Ok(HttpResponse::Ok().json(search_result))
|
Ok(HttpResponse::Ok().json(search_result))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn search_with_post(
|
pub async fn search_with_post(
|
||||||
data: GuardedData<Public, Data>,
|
data: GuardedData<Public, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
params: web::Json<SearchQuery>,
|
params: web::Json<SearchQuery>,
|
||||||
|
@ -9,7 +9,7 @@ use crate::{error::ResponseError, index::Unchecked};
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! make_setting_route {
|
macro_rules! make_setting_route {
|
||||||
($route:literal, $type:ty, $attr:ident, $camelcase_attr:literal) => {
|
($route:literal, $type:ty, $attr:ident, $camelcase_attr:literal) => {
|
||||||
mod $attr {
|
pub mod $attr {
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use actix_web::{web, HttpResponse, Resource};
|
use actix_web::{web, HttpResponse, Resource};
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ macro_rules! make_setting_route {
|
|||||||
use crate::index::Settings;
|
use crate::index::Settings;
|
||||||
use crate::extractors::authentication::{GuardedData, policies::*};
|
use crate::extractors::authentication::{GuardedData, policies::*};
|
||||||
|
|
||||||
async fn delete(
|
pub async fn delete(
|
||||||
data: GuardedData<Private, data::Data>,
|
data: GuardedData<Private, data::Data>,
|
||||||
index_uid: web::Path<String>,
|
index_uid: web::Path<String>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -32,7 +32,7 @@ macro_rules! make_setting_route {
|
|||||||
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(
|
pub async fn update(
|
||||||
data: GuardedData<Private, data::Data>,
|
data: GuardedData<Private, data::Data>,
|
||||||
index_uid: actix_web::web::Path<String>,
|
index_uid: actix_web::web::Path<String>,
|
||||||
body: actix_web::web::Json<Option<$type>>,
|
body: actix_web::web::Json<Option<$type>>,
|
||||||
@ -47,7 +47,7 @@ macro_rules! make_setting_route {
|
|||||||
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get(
|
pub async fn get(
|
||||||
data: GuardedData<Private, data::Data>,
|
data: GuardedData<Private, data::Data>,
|
||||||
index_uid: actix_web::web::Path<String>,
|
index_uid: actix_web::web::Path<String>,
|
||||||
) -> std::result::Result<HttpResponse, ResponseError> {
|
) -> std::result::Result<HttpResponse, ResponseError> {
|
||||||
@ -135,7 +135,7 @@ generate_configure!(
|
|||||||
ranking_rules
|
ranking_rules
|
||||||
);
|
);
|
||||||
|
|
||||||
async fn update_all(
|
pub async fn update_all(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
index_uid: web::Path<String>,
|
index_uid: web::Path<String>,
|
||||||
body: web::Json<Settings<Unchecked>>,
|
body: web::Json<Settings<Unchecked>>,
|
||||||
@ -149,7 +149,7 @@ async fn update_all(
|
|||||||
Ok(HttpResponse::Accepted().json(json))
|
Ok(HttpResponse::Accepted().json(json))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all(
|
pub async fn get_all(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
index_uid: web::Path<String>,
|
index_uid: web::Path<String>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -158,7 +158,7 @@ async fn get_all(
|
|||||||
Ok(HttpResponse::Ok().json(settings))
|
Ok(HttpResponse::Ok().json(settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_all(
|
pub async fn delete_all(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
index_uid: web::Path<String>,
|
index_uid: web::Path<String>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
@ -31,12 +31,12 @@ pub struct UpdateIndexResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct UpdateParam {
|
pub struct UpdateParam {
|
||||||
index_uid: String,
|
index_uid: String,
|
||||||
update_id: u64,
|
update_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_update_status(
|
pub async fn get_update_status(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<UpdateParam>,
|
path: web::Path<UpdateParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -49,7 +49,7 @@ async fn get_update_status(
|
|||||||
Ok(HttpResponse::Ok().json(meta))
|
Ok(HttpResponse::Ok().json(meta))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_updates_status(
|
pub async fn get_all_updates_status(
|
||||||
data: GuardedData<Private, Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
@ -278,3 +278,97 @@ pub async fn list_keys(data: GuardedData<Admin, Data>) -> HttpResponse {
|
|||||||
pub async fn get_health() -> Result<HttpResponse, ResponseError> {
|
pub async fn get_health() -> Result<HttpResponse, ResponseError> {
|
||||||
Ok(HttpResponse::Ok().json(serde_json::json!({ "status": "available" })))
|
Ok(HttpResponse::Ok().json(serde_json::json!({ "status": "available" })))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::data::Data;
|
||||||
|
use crate::extractors::authentication::GuardedData;
|
||||||
|
|
||||||
|
/// A type implemented for a route that uses a authentication policy `Policy`.
|
||||||
|
///
|
||||||
|
/// This trait is used for regression testing of route authenticaton policies.
|
||||||
|
trait Is<Policy, T> {}
|
||||||
|
|
||||||
|
macro_rules! impl_is_policy {
|
||||||
|
($($param:ident)*) => {
|
||||||
|
impl<Policy, Func, $($param,)* Res> Is<Policy, (($($param,)*), Res)> for Func
|
||||||
|
where Func: Fn(GuardedData<Policy, Data>, $($param,)*) -> Res {}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_is_policy! {}
|
||||||
|
impl_is_policy! {A}
|
||||||
|
impl_is_policy! {A B}
|
||||||
|
impl_is_policy! {A B C}
|
||||||
|
impl_is_policy! {A B C D}
|
||||||
|
|
||||||
|
/// Emits a compile error if a route doesn't have the correct authentication policy.
|
||||||
|
///
|
||||||
|
/// This works by trying to cast the route function into a Is<Policy, _> type, where Policy it
|
||||||
|
/// the authentication policy defined for the route.
|
||||||
|
macro_rules! test_auth_routes {
|
||||||
|
($($policy:ident => { $($route:expr,)*})*) => {
|
||||||
|
#[test]
|
||||||
|
fn test_auth() {
|
||||||
|
$($(let _: &dyn Is<$policy, _> = &$route;)*)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test_auth_routes! {
|
||||||
|
Public => {
|
||||||
|
indexes::search::search_with_url_query,
|
||||||
|
indexes::search::search_with_post,
|
||||||
|
|
||||||
|
indexes::documents::get_document,
|
||||||
|
indexes::documents::get_all_documents,
|
||||||
|
}
|
||||||
|
Private => {
|
||||||
|
get_stats,
|
||||||
|
get_version,
|
||||||
|
|
||||||
|
indexes::create_index,
|
||||||
|
indexes::list_indexes,
|
||||||
|
indexes::get_index_stats,
|
||||||
|
indexes::delete_index,
|
||||||
|
indexes::update_index,
|
||||||
|
indexes::get_index,
|
||||||
|
|
||||||
|
dump::create_dump,
|
||||||
|
|
||||||
|
indexes::settings::filterable_attributes::get,
|
||||||
|
indexes::settings::displayed_attributes::get,
|
||||||
|
indexes::settings::searchable_attributes::get,
|
||||||
|
indexes::settings::stop_words::get,
|
||||||
|
indexes::settings::synonyms::get,
|
||||||
|
indexes::settings::distinct_attribute::get,
|
||||||
|
indexes::settings::filterable_attributes::update,
|
||||||
|
indexes::settings::displayed_attributes::update,
|
||||||
|
indexes::settings::searchable_attributes::update,
|
||||||
|
indexes::settings::stop_words::update,
|
||||||
|
indexes::settings::synonyms::update,
|
||||||
|
indexes::settings::distinct_attribute::update,
|
||||||
|
indexes::settings::filterable_attributes::delete,
|
||||||
|
indexes::settings::displayed_attributes::delete,
|
||||||
|
indexes::settings::searchable_attributes::delete,
|
||||||
|
indexes::settings::stop_words::delete,
|
||||||
|
indexes::settings::synonyms::delete,
|
||||||
|
indexes::settings::distinct_attribute::delete,
|
||||||
|
indexes::settings::delete_all,
|
||||||
|
indexes::settings::get_all,
|
||||||
|
indexes::settings::update_all,
|
||||||
|
|
||||||
|
indexes::documents::clear_all_documents,
|
||||||
|
indexes::documents::delete_documents,
|
||||||
|
indexes::documents::update_documents,
|
||||||
|
indexes::documents::add_documents,
|
||||||
|
indexes::documents::delete_document,
|
||||||
|
|
||||||
|
indexes::updates::get_all_updates_status,
|
||||||
|
indexes::updates::get_update_status,
|
||||||
|
}
|
||||||
|
Admin => { list_keys, }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user