diff --git a/meilisearch-auth/src/error.rs b/meilisearch-auth/src/error.rs index dc6301348..dbf28b421 100644 --- a/meilisearch-auth/src/error.rs +++ b/meilisearch-auth/src/error.rs @@ -22,12 +22,14 @@ pub enum AuthControllerError { "`name` field value `{0}` is invalid. It should be a string or specified as a null value." )] InvalidApiKeyName(Value), - #[error("`uid` field value `{0}` is invalid. It should be a valid uuidv4 string or ommited.")] + #[error("`uid` field value `{0}` is invalid. It should be a valid UUID v4 string or omitted.")] InvalidApiKeyUid(Value), #[error("API key `{0}` not found.")] ApiKeyNotFound(String), - #[error("`uid` field value `{0}` already exists for an API key.")] + #[error("`uid` field value `{0}` is already an existing API key.")] ApiKeyAlreadyExists(String), + #[error("`{0}` field cannot be modified for the given resource.")] + ImmutableField(String), #[error("Internal error: {0}")] Internal(Box), } @@ -51,6 +53,7 @@ impl ErrorCode for AuthControllerError { Self::ApiKeyNotFound(_) => Code::ApiKeyNotFound, Self::InvalidApiKeyUid(_) => Code::InvalidApiKeyUid, Self::ApiKeyAlreadyExists(_) => Code::ApiKeyAlreadyExists, + Self::ImmutableField(_) => Code::ImmutableField, Self::Internal(_) => Code::Internal, } } diff --git a/meilisearch-auth/src/key.rs b/meilisearch-auth/src/key.rs index baac68637..f6ff7096c 100644 --- a/meilisearch-auth/src/key.rs +++ b/meilisearch-auth/src/key.rs @@ -99,6 +99,30 @@ impl Key { self.name = des?; } + if value.get("uid").is_some() { + return Err(AuthControllerError::ImmutableField("uid".to_string())); + } + + if value.get("actions").is_some() { + return Err(AuthControllerError::ImmutableField("actions".to_string())); + } + + if value.get("indexes").is_some() { + return Err(AuthControllerError::ImmutableField("indexes".to_string())); + } + + if value.get("expiresAt").is_some() { + return Err(AuthControllerError::ImmutableField("expiresAt".to_string())); + } + + if value.get("createdAt").is_some() { + return Err(AuthControllerError::ImmutableField("createdAt".to_string())); + } + + if value.get("updatedAt").is_some() { + return Err(AuthControllerError::ImmutableField("updatedAt".to_string())); + } + self.updated_at = OffsetDateTime::now_utc(); Ok(()) diff --git a/meilisearch-error/src/lib.rs b/meilisearch-error/src/lib.rs index 57882f8e0..6e6273db2 100644 --- a/meilisearch-error/src/lib.rs +++ b/meilisearch-error/src/lib.rs @@ -168,6 +168,7 @@ pub enum Code { InvalidApiKeyDescription, InvalidApiKeyName, InvalidApiKeyUid, + ImmutableField, ApiKeyAlreadyExists, } @@ -278,6 +279,7 @@ impl Code { InvalidApiKeyName => ErrCode::invalid("invalid_api_key_name", StatusCode::BAD_REQUEST), InvalidApiKeyUid => ErrCode::invalid("invalid_api_key_uid", StatusCode::BAD_REQUEST), ApiKeyAlreadyExists => ErrCode::invalid("api_key_already_exists", StatusCode::CONFLICT), + ImmutableField => ErrCode::invalid("immutable_field", StatusCode::BAD_REQUEST), InvalidMinWordLengthForTypo => { ErrCode::invalid("invalid_min_word_length_for_typo", StatusCode::BAD_REQUEST) } diff --git a/meilisearch-http/tests/auth/api_keys.rs b/meilisearch-http/tests/auth/api_keys.rs index 2d6f07d86..a9f2bf91d 100644 --- a/meilisearch-http/tests/auth/api_keys.rs +++ b/meilisearch-http/tests/auth/api_keys.rs @@ -470,7 +470,7 @@ async fn error_add_api_key_invalid_parameters_uid() { assert_eq!(400, code, "{:?}", &response); let expected_response = json!({ - "message": r#"`uid` field value `"aaaaabbbbbccc"` is invalid. It should be a valid uuidv4 string or ommited."#, + "message": r#"`uid` field value `"aaaaabbbbbccc"` is invalid. It should be a valid UUID v4 string or omitted."#, "code": "invalid_api_key_uid", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_uid" @@ -499,7 +499,7 @@ async fn error_add_api_key_parameters_uid_already_exist() { assert_eq!(409, code, "{:?}", &response); let expected_response = json!({ - "message": "`uid` field value `4bc0887a-0e41-4f3b-935d-0c451dcee9c8` already exists for an API key.", + "message": "`uid` field value `4bc0887a-0e41-4f3b-935d-0c451dcee9c8` is already an existing API key.", "code": "api_key_already_exists", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#api_key_already_exists" @@ -1112,7 +1112,7 @@ async fn patch_api_key_name() { } #[actix_rt::test] -async fn patch_api_key_indexes_unchanged() { +async fn error_patch_api_key_indexes() { let mut server = Server::new_auth().await; server.use_api_key("MASTER_KEY"); @@ -1143,44 +1143,24 @@ async fn patch_api_key_indexes_unchanged() { assert!(response["updatedAt"].is_string()); let uid = response["uid"].as_str().unwrap(); - let created_at = response["createdAt"].as_str().unwrap(); - let updated_at = response["updatedAt"].as_str().unwrap(); let content = json!({ "indexes": ["products", "prices"] }); thread::sleep(time::Duration::new(1, 0)); let (response, code) = server.patch_api_key(&uid, content).await; - assert_eq!(200, code, "{:?}", &response); - assert!(response["key"].is_string()); - assert!(response["expiresAt"].is_string()); - assert!(response["createdAt"].is_string()); - assert_ne!(response["updatedAt"].as_str().unwrap(), updated_at); - assert_eq!(response["createdAt"].as_str().unwrap(), created_at); + assert_eq!(400, code, "{:?}", &response); - let expected = json!({ - "description": "Indexing API key", - "indexes": ["products"], - "actions": [ - "search", - "documents.add", - "documents.get", - "documents.delete", - "indexes.create", - "indexes.get", - "indexes.update", - "indexes.delete", - "stats.get", - "dumps.create", - "dumps.get" - ], - "expiresAt": "2050-11-13T00:00:00Z" + let expected = json!({"message": "`indexes` field cannot be modified for the given resource.", + "code": "immutable_field", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#immutable_field" }); assert_json_include!(actual: response, expected: expected); } #[actix_rt::test] -async fn patch_api_key_actions_unchanged() { +async fn error_patch_api_key_actions() { let mut server = Server::new_auth().await; server.use_api_key("MASTER_KEY"); @@ -1212,9 +1192,6 @@ async fn patch_api_key_actions_unchanged() { let uid = response["uid"].as_str().unwrap(); - let created_at = response["createdAt"].as_str().unwrap(); - let updated_at = response["updatedAt"].as_str().unwrap(); - let content = json!({ "actions": [ "search", @@ -1227,37 +1204,19 @@ async fn patch_api_key_actions_unchanged() { thread::sleep(time::Duration::new(1, 0)); let (response, code) = server.patch_api_key(&uid, content).await; - assert_eq!(200, code, "{:?}", &response); - assert!(response["key"].is_string()); - assert!(response["expiresAt"].is_string()); - assert!(response["createdAt"].is_string()); - assert_ne!(response["updatedAt"].as_str().unwrap(), updated_at); - assert_eq!(response["createdAt"].as_str().unwrap(), created_at); + assert_eq!(400, code, "{:?}", &response); - let expected = json!({ - "description": "Indexing API key", - "indexes": ["products"], - "actions": [ - "search", - "documents.add", - "documents.get", - "documents.delete", - "indexes.create", - "indexes.get", - "indexes.update", - "indexes.delete", - "stats.get", - "dumps.create", - "dumps.get" - ], - "expiresAt": "2050-11-13T00:00:00Z" + let expected = json!({"message": "`actions` field cannot be modified for the given resource.", + "code": "immutable_field", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#immutable_field" }); assert_json_include!(actual: response, expected: expected); } #[actix_rt::test] -async fn patch_api_key_expiration_date_unchanged() { +async fn error_patch_api_key_expiration_date() { let mut server = Server::new_auth().await; server.use_api_key("MASTER_KEY"); @@ -1288,37 +1247,17 @@ async fn patch_api_key_expiration_date_unchanged() { assert!(response["updatedAt"].is_string()); let uid = response["uid"].as_str().unwrap(); - let created_at = response["createdAt"].as_str().unwrap(); - let updated_at = response["updatedAt"].as_str().unwrap(); let content = json!({ "expiresAt": "2055-11-13T00:00:00Z" }); thread::sleep(time::Duration::new(1, 0)); let (response, code) = server.patch_api_key(&uid, content).await; - assert_eq!(200, code, "{:?}", &response); - assert!(response["key"].is_string()); - assert!(response["expiresAt"].is_string()); - assert!(response["createdAt"].is_string()); - assert_ne!(response["updatedAt"].as_str().unwrap(), updated_at); - assert_eq!(response["createdAt"].as_str().unwrap(), created_at); + assert_eq!(400, code, "{:?}", &response); - let expected = json!({ - "description": "Indexing API key", - "indexes": ["products"], - "actions": [ - "search", - "documents.add", - "documents.get", - "documents.delete", - "indexes.create", - "indexes.get", - "indexes.update", - "indexes.delete", - "stats.get", - "dumps.create", - "dumps.get" - ], - "expiresAt": "2050-11-13T00:00:00Z" + let expected = json!({"message": "`expiresAt` field cannot be modified for the given resource.", + "code": "immutable_field", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#immutable_field" }); assert_json_include!(actual: response, expected: expected); diff --git a/meilisearch-http/tests/auth/authorization.rs b/meilisearch-http/tests/auth/authorization.rs index fde4c61f3..2080e2990 100644 --- a/meilisearch-http/tests/auth/authorization.rs +++ b/meilisearch-http/tests/auth/authorization.rs @@ -149,31 +149,22 @@ async fn error_access_unauthorized_index() { #[cfg_attr(target_os = "windows", ignore)] async fn error_access_unauthorized_action() { let mut server = Server::new_auth().await; - server.use_api_key("MASTER_KEY"); - - let content = json!({ - "indexes": ["products"], - "actions": [], - "expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(), - }); - - let (response, code) = server.add_api_key(content).await; - assert_eq!(201, code, "{:?}", &response); - assert!(response["key"].is_string()); - - let key = response["key"].as_str().unwrap(); - server.use_api_key(&key); for ((method, route), action) in AUTHORIZATIONS.iter() { + // create a new API key letting only the needed action. server.use_api_key("MASTER_KEY"); - // Patch API key letting all rights but the needed one. let content = json!({ + "indexes": ["products"], "actions": ALL_ACTIONS.difference(action).collect::>(), + "expiresAt": (OffsetDateTime::now_utc() + Duration::hours(1)).format(&Rfc3339).unwrap(), }); - let (response, code) = server.patch_api_key(&key, content).await; - assert_eq!(200, code, "{:?}", &response); + let (response, code) = server.add_api_key(content).await; + assert_eq!(201, code, "{:?}", &response); + assert!(response["key"].is_string()); + + let key = response["key"].as_str().unwrap(); server.use_api_key(&key); let (response, code) = server.dummy_request(method, route).await;