diff --git a/compose/voms-deploy/assets/db/iam-test-dump.sql b/compose/voms-deploy/assets/db/iam-test-dump.sql index 134ca67138..597b43863b 100644 --- a/compose/voms-deploy/assets/db/iam-test-dump.sql +++ b/compose/voms-deploy/assets/db/iam-test-dump.sql @@ -656,7 +656,7 @@ CREATE TABLE `client_details` ( LOCK TABLES `client_details` WRITE; /*!40000 ALTER TABLE `client_details` DISABLE KEYS */; -INSERT INTO `client_details` VALUES (1,NULL,1,0,1,600,'client','secret',3600,NULL,NULL,'Test Client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(2,NULL,1,0,1,0,'tasks-app','secret',0,NULL,NULL,'Tasks App','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(3,NULL,1,0,1,600,'post-client','secret',3600,NULL,NULL,'Post client','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(4,NULL,1,0,1,600,'client-cred','secret',3600,NULL,NULL,'Client credentials','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(5,NULL,1,0,1,600,'password-grant','secret',3600,NULL,NULL,'Password grant client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(6,NULL,1,0,1,600,'scim-client-ro','secret',3600,NULL,NULL,'SCIM client (read-only)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(7,NULL,1,0,1,600,'scim-client-rw','secret',3600,NULL,NULL,'SCIM client (read-write)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(8,NULL,1,0,1,600,'token-exchange-actor','secret',3600,NULL,NULL,'Token Exchange grant client actor','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(9,NULL,1,0,1,600,'token-exchange-subject','secret',3600,NULL,NULL,'Token Exchange grant client subject','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(10,NULL,1,0,1,600,'registration-client','secret',3600,NULL,NULL,'Registration service test client','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(11,NULL,1,0,1,600,'token-lookup-client','secret',3600,NULL,NULL,'Token lookup client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(12,NULL,1,0,1,600,'device-code-client','secret',3600,NULL,NULL,'Device code client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,600),(13,NULL,1,0,0,600,'implicit-flow-client',NULL,3600,NULL,NULL,'Implicit Flow client',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,600),(14,NULL,1,0,0,600,'public-dc-client',NULL,3600,NULL,NULL,'Public Device Code client',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,600),(15,NULL,1,0,1,600,'jwt-auth-client_secret_jwt','c8e9eed0-e6e4-4a66-b16e-6f37096356a7',3600,NULL,NULL,'JWT Bearer Auth Client (client_secret_jwt)','SECRET_JWT',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'HS256',NULL,0,NULL,NULL,1,NULL,NULL,NULL,NULL,NULL),(16,NULL,1,0,1,600,'jwt-auth-private_key_jwt','secret',3600,NULL,NULL,'JWT Bearer Auth Client (private_key_jwt)','PRIVATE_KEY',NULL,NULL,NULL,NULL,NULL,NULL,'{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"kid\":\"rsa1\",\"n\":\"1y1CP181zqPNPlV1JDM7Xv0QnGswhSTHe8_XPZHxDTJkykpk_1BmgA3ovP62QRE2ORgsv5oSBI_Z_RaOc4Zx2FonjEJF2oBHtBjsAiF-pxGkM5ZPjFNgFTGp1yUUBjFDcEeIGCwPEyYSt93sQIP_0DRbViMUnpyn3xgM_a1dO5brEWR2n1Uqff1yA5NXfLS03qpl2dpH4HFY5-Zs4bvtJykpAOhoHuIQbz-hmxb9MZ3uTAwsx2HiyEJtz-suyTBHO3BM2o8UcCeyfa34ShPB8i86-sf78fOk2KeRIW1Bju3ANmdV3sxL0j29cesxKCZ06u2ZiGR3Srbft8EdLPzf-w\"}]}',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'RS256',NULL,0,NULL,NULL,1,NULL,NULL,NULL,NULL,NULL),(17,NULL,1,0,1,600,'admin-client-ro','secret',3600,NULL,NULL,'Admin client (read-only)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(18,NULL,1,0,1,600,'admin-client-rw','secret',3600,NULL,NULL,'Admin client (read-write)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL); +INSERT INTO `client_details` VALUES (1,NULL,1,0,1,600,'client','secret',3600,NULL,NULL,'Test Client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(2,NULL,1,0,1,0,'tasks-app','secret',0,NULL,NULL,'Tasks App','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(3,NULL,1,0,1,600,'post-client','secret',3600,NULL,NULL,'Post client','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(4,NULL,1,0,1,600,'client-cred','secret',3600,NULL,NULL,'Client credentials','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(5,NULL,1,0,1,600,'password-grant','secret',3600,NULL,NULL,'Password grant client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(6,NULL,1,0,1,600,'scim-client-ro','secret',3600,NULL,NULL,'SCIM client (read-only)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(7,NULL,1,0,1,600,'scim-client-rw','secret',3600,NULL,NULL,'SCIM client (read-write)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(8,NULL,1,0,1,600,'token-exchange-actor','secret',3600,NULL,NULL,'Token Exchange grant client actor','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(9,NULL,1,0,1,600,'token-exchange-subject','secret',3600,NULL,NULL,'Token Exchange grant client subject','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(10,NULL,1,0,1,600,'registration-client','secret',3600,NULL,NULL,'Registration service test client','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(11,NULL,1,0,1,600,'token-lookup-client','secret',3600,NULL,NULL,'Token lookup client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(12,NULL,1,0,1,600,'device-code-client','secret',3600,NULL,NULL,'Device code client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,600),(13,NULL,1,0,0,600,'implicit-flow-client',NULL,3600,NULL,NULL,'Implicit Flow client',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,600),(14,NULL,1,0,0,600,'public-dc-client',NULL,3600,NULL,NULL,'Public Device Code client',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,600),(15,NULL,1,0,1,600,'jwt-auth-client_secret_jwt','secret',3600,NULL,NULL,'JWT Bearer Auth Client (client_secret_jwt)','SECRET_JWT',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'HS256',NULL,0,NULL,NULL,1,NULL,NULL,NULL,NULL,NULL),(16,NULL,1,0,1,600,'jwt-auth-private_key_jwt','secret',3600,NULL,NULL,'JWT Bearer Auth Client (private_key_jwt)','PRIVATE_KEY',NULL,NULL,NULL,NULL,NULL,NULL,'{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"kid\":\"rsa1\",\"n\":\"1y1CP181zqPNPlV1JDM7Xv0QnGswhSTHe8_XPZHxDTJkykpk_1BmgA3ovP62QRE2ORgsv5oSBI_Z_RaOc4Zx2FonjEJF2oBHtBjsAiF-pxGkM5ZPjFNgFTGp1yUUBjFDcEeIGCwPEyYSt93sQIP_0DRbViMUnpyn3xgM_a1dO5brEWR2n1Uqff1yA5NXfLS03qpl2dpH4HFY5-Zs4bvtJykpAOhoHuIQbz-hmxb9MZ3uTAwsx2HiyEJtz-suyTBHO3BM2o8UcCeyfa34ShPB8i86-sf78fOk2KeRIW1Bju3ANmdV3sxL0j29cesxKCZ06u2ZiGR3Srbft8EdLPzf-w\"}]}',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'RS256',NULL,0,NULL,NULL,1,NULL,NULL,NULL,NULL,NULL),(17,NULL,1,0,1,600,'admin-client-ro','secret',3600,NULL,NULL,'Admin client (read-only)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL),(18,NULL,1,0,1,600,'admin-client-rw','secret',3600,NULL,NULL,'Admin client (read-write)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-01 10:57:22',NULL,1,NULL,NULL,NULL,NULL,NULL); /*!40000 ALTER TABLE `client_details` ENABLE KEYS */; UNLOCK TABLES; diff --git a/compose/voms-replica/assets/db-primary/iam-test-dump.sql b/compose/voms-replica/assets/db-primary/iam-test-dump.sql index f4bc1e825f..88527b266d 100644 --- a/compose/voms-replica/assets/db-primary/iam-test-dump.sql +++ b/compose/voms-replica/assets/db-primary/iam-test-dump.sql @@ -656,7 +656,7 @@ CREATE TABLE `client_details` ( LOCK TABLES `client_details` WRITE; /*!40000 ALTER TABLE `client_details` DISABLE KEYS */; -INSERT INTO `client_details` VALUES (1,NULL,1,0,1,600,'client','secret',3600,NULL,NULL,'Test Client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(2,NULL,1,0,1,0,'tasks-app','secret',0,NULL,NULL,'Tasks App','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(3,NULL,1,0,1,600,'post-client','secret',3600,NULL,NULL,'Post client','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(4,NULL,1,0,1,600,'client-cred','secret',3600,NULL,NULL,'Client credentials','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(5,NULL,1,0,1,600,'password-grant','secret',3600,NULL,NULL,'Password grant client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(6,NULL,1,0,1,600,'scim-client-ro','secret',3600,NULL,NULL,'SCIM client (read-only)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,600),(7,NULL,1,0,1,600,'scim-client-rw','secret',3600,NULL,NULL,'SCIM client (read-write)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,600),(8,NULL,1,0,1,600,'token-exchange-actor','secret',3600,NULL,NULL,'Token Exchange grant client actor','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(9,NULL,1,0,1,600,'token-exchange-subject','secret',3600,NULL,NULL,'Token Exchange grant client subject','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(10,NULL,1,0,1,600,'registration-client','secret',3600,NULL,NULL,'Registration service test client','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(11,NULL,1,0,1,600,'token-lookup-client','secret',3600,NULL,NULL,'Token lookup client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(12,NULL,1,0,1,600,'device-code-client','secret',3600,NULL,NULL,'Device code client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,600),(13,NULL,1,0,0,600,'implicit-flow-client',NULL,3600,NULL,NULL,'Implicit Flow client',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,600),(14,NULL,1,0,0,600,'public-dc-client',NULL,3600,NULL,NULL,'Public Device Code client',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,600),(15,NULL,1,0,1,600,'jwt-auth-client_secret_jwt','c8e9eed0-e6e4-4a66-b16e-6f37096356a7',3600,NULL,NULL,'JWT Bearer Auth Client (client_secret_jwt)','SECRET_JWT',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'HS256',NULL,0,NULL,NULL,1,NULL,NULL,NULL,NULL,NULL),(16,NULL,1,0,1,600,'jwt-auth-private_key_jwt','secret',3600,NULL,NULL,'JWT Bearer Auth Client (private_key_jwt)','PRIVATE_KEY',NULL,NULL,NULL,NULL,NULL,NULL,'{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"kid\":\"rsa1\",\"n\":\"1y1CP181zqPNPlV1JDM7Xv0QnGswhSTHe8_XPZHxDTJkykpk_1BmgA3ovP62QRE2ORgsv5oSBI_Z_RaOc4Zx2FonjEJF2oBHtBjsAiF-pxGkM5ZPjFNgFTGp1yUUBjFDcEeIGCwPEyYSt93sQIP_0DRbViMUnpyn3xgM_a1dO5brEWR2n1Uqff1yA5NXfLS03qpl2dpH4HFY5-Zs4bvtJykpAOhoHuIQbz-hmxb9MZ3uTAwsx2HiyEJtz-suyTBHO3BM2o8UcCeyfa34ShPB8i86-sf78fOk2KeRIW1Bju3ANmdV3sxL0j29cesxKCZ06u2ZiGR3Srbft8EdLPzf-w\"}]}',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'RS256',NULL,0,NULL,NULL,1,NULL,NULL,NULL,NULL,NULL),(17,NULL,1,0,1,600,'admin-client-ro','secret',3600,NULL,NULL,'Admin client (read-only)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(18,NULL,1,0,1,600,'admin-client-rw','secret',3600,NULL,NULL,'Admin client (read-write)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(19,NULL,1,0,1,600,'public-client',NULL,3600,3600,NULL,'Public client','NONE',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL); +INSERT INTO `client_details` VALUES (1,NULL,1,0,1,600,'client','secret',3600,NULL,NULL,'Test Client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(2,NULL,1,0,1,0,'tasks-app','secret',0,NULL,NULL,'Tasks App','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(3,NULL,1,0,1,600,'post-client','secret',3600,NULL,NULL,'Post client','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(4,NULL,1,0,1,600,'client-cred','secret',3600,NULL,NULL,'Client credentials','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(5,NULL,1,0,1,600,'password-grant','secret',3600,NULL,NULL,'Password grant client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(6,NULL,1,0,1,600,'scim-client-ro','secret',3600,NULL,NULL,'SCIM client (read-only)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,600),(7,NULL,1,0,1,600,'scim-client-rw','secret',3600,NULL,NULL,'SCIM client (read-write)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,600),(8,NULL,1,0,1,600,'token-exchange-actor','secret',3600,NULL,NULL,'Token Exchange grant client actor','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(9,NULL,1,0,1,600,'token-exchange-subject','secret',3600,NULL,NULL,'Token Exchange grant client subject','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(10,NULL,1,0,1,600,'registration-client','secret',3600,NULL,NULL,'Registration service test client','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(11,NULL,1,0,1,600,'token-lookup-client','secret',3600,NULL,NULL,'Token lookup client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(12,NULL,1,0,1,600,'device-code-client','secret',3600,NULL,NULL,'Device code client','SECRET_BASIC',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,600),(13,NULL,1,0,0,600,'implicit-flow-client',NULL,3600,NULL,NULL,'Implicit Flow client',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,600),(14,NULL,1,0,0,600,'public-dc-client',NULL,3600,NULL,NULL,'Public Device Code client',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,600),(15,NULL,1,0,1,600,'jwt-auth-client_secret_jwt','secret',3600,NULL,NULL,'JWT Bearer Auth Client (client_secret_jwt)','SECRET_JWT',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'HS256',NULL,0,NULL,NULL,1,NULL,NULL,NULL,NULL,NULL),(16,NULL,1,0,1,600,'jwt-auth-private_key_jwt','secret',3600,NULL,NULL,'JWT Bearer Auth Client (private_key_jwt)','PRIVATE_KEY',NULL,NULL,NULL,NULL,NULL,NULL,'{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"kid\":\"rsa1\",\"n\":\"1y1CP181zqPNPlV1JDM7Xv0QnGswhSTHe8_XPZHxDTJkykpk_1BmgA3ovP62QRE2ORgsv5oSBI_Z_RaOc4Zx2FonjEJF2oBHtBjsAiF-pxGkM5ZPjFNgFTGp1yUUBjFDcEeIGCwPEyYSt93sQIP_0DRbViMUnpyn3xgM_a1dO5brEWR2n1Uqff1yA5NXfLS03qpl2dpH4HFY5-Zs4bvtJykpAOhoHuIQbz-hmxb9MZ3uTAwsx2HiyEJtz-suyTBHO3BM2o8UcCeyfa34ShPB8i86-sf78fOk2KeRIW1Bju3ANmdV3sxL0j29cesxKCZ06u2ZiGR3Srbft8EdLPzf-w\"}]}',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'RS256',NULL,0,NULL,NULL,1,NULL,NULL,NULL,NULL,NULL),(17,NULL,1,0,1,600,'admin-client-ro','secret',3600,NULL,NULL,'Admin client (read-only)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(18,NULL,1,0,1,600,'admin-client-rw','secret',3600,NULL,NULL,'Admin client (read-write)','SECRET_POST',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL),(19,NULL,1,0,1,600,'public-client',NULL,3600,3600,NULL,'Public client','NONE',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,'2024-03-27 17:43:07',NULL,1,NULL,NULL,NULL,NULL,NULL); /*!40000 ALTER TABLE `client_details` ENABLE KEYS */; UNLOCK TABLES; diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java index 60231fb9a0..dc150475a0 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java @@ -159,7 +159,7 @@ public void disableClient(@PathVariable String clientId) { @PostMapping("/{clientId}/secret") @ResponseStatus(CREATED) - @PreAuthorize("#iam.hasScope('iam:admin.write') or #iam.hasDashboardRole('ROLE_ADMIN')") + @PreAuthorize("#iam.hasScope('iam:admin.write') or #iam.hasDashboardRole('ROLE_ADMIN') or #iam.isClientOwner(#clientId)") public RegisteredClientDTO rotateClientSecret(@PathVariable String clientId) { return managementService.generateNewClientSecret(clientId); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java index 1055aca9c3..a9856b6fbe 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java @@ -61,6 +61,7 @@ import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAccountClient; import it.infn.mw.iam.persistence.repository.IamAccountRepository; +import it.infn.mw.iam.util.IamClientSecretEncoder; @Service @Validated @@ -120,15 +121,20 @@ public Optional retrieveClientByClientId(String clientId) { @Override public RegisteredClientDTO saveNewClient(RegisteredClientDTO client) throws ParseException { + String secret = defaultsService.generateClientSecret(); + ClientDetailsEntity entity = converter.entityFromClientManagementRequest(client); entity.setDynamicallyRegistered(false); entity.setCreatedAt(Date.from(clock.instant())); + entity.setClientSecret(new IamClientSecretEncoder().encode(secret)); entity.setActive(true); defaultsService.setupClientDefaults(entity); entity = clientService.saveNewClient(entity); - return converter.registeredClientDtoFromEntity(entity); + RegisteredClientDTO newClientResponse = converter.registeredClientDtoFromEntity(entity); + newClientResponse.setClientSecret(secret); + return newClientResponse; } @Override @@ -162,14 +168,13 @@ private List getClientOwners(String clientId) { @Validated(OnClientUpdate.class) @Override - public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO client) + public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO clientDTO) throws ParseException { ClientDetailsEntity oldClient = clientService.findClientByClientId(clientId) .orElseThrow(ClientSuppliers.clientNotFound(clientId)); - ClientDetailsEntity newClient = converter.entityFromClientManagementRequest(client); - + ClientDetailsEntity newClient = converter.entityFromClientManagementRequest(clientDTO); newClient.setId(oldClient.getId()); newClient.setCreatedAt(oldClient.getCreatedAt()); newClient.setClientId(oldClient.getClientId()); @@ -179,8 +184,11 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO cli if (NONE.equals(newClient.getTokenEndpointAuthMethod())) { newClient.setClientSecret(null); - } else if (isNull(client.getClientSecret())) { - client.setClientSecret(defaultsService.generateClientSecret()); + } else if (isNull(clientDTO.getClientSecret()) && isNull(oldClient.getClientSecret())) { + newClient.setClientSecret(defaultsService.generateClientSecret()); + } else { + // Direct updates are disabled. Changes must be made via secret reset process + newClient.setClientSecret(oldClient.getClientSecret()); } newClient = clientService.updateClient(newClient); @@ -210,10 +218,13 @@ public RegisteredClientDTO generateNewClientSecret(String clientId) { ClientDetailsEntity client = clientService.findClientByClientId(clientId) .orElseThrow(ClientSuppliers.clientNotFound(clientId)); - client.setClientSecret(defaultsService.generateClientSecret()); + String pwd = defaultsService.generateClientSecret(); + client.setClientSecret(new IamClientSecretEncoder().encode(pwd)); client = clientService.updateClient(client); eventPublisher.publishEvent(new ClientSecretUpdatedEvent(this, client)); - return converter.registeredClientDtoFromEntity(client); + RegisteredClientDTO clientWithSecret = converter.registeredClientDtoFromEntity(client); + clientWithSecret.setClientSecret(pwd); + return clientWithSecret; } @Override diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java index e44a812835..42316ce05d 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java @@ -19,6 +19,7 @@ import static it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy.ADMINISTRATORS; import static it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy.ANYONE; import static it.infn.mw.iam.config.client_registration.ClientRegistrationProperties.ClientRegistrationAuthorizationPolicy.REGISTERED_USERS; +import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.NONE; import static java.util.Objects.isNull; import static java.util.stream.Collectors.toSet; @@ -47,8 +48,8 @@ import org.springframework.validation.annotation.Validated; import it.infn.mw.iam.api.account.AccountUtils; -import it.infn.mw.iam.api.client.error.InvalidClientRegistrationRequest; import it.infn.mw.iam.api.client.error.ClientSuspended; +import it.infn.mw.iam.api.client.error.InvalidClientRegistrationRequest; import it.infn.mw.iam.api.client.registration.validation.OnDynamicClientRegistration; import it.infn.mw.iam.api.client.registration.validation.OnDynamicClientUpdate; import it.infn.mw.iam.api.client.service.ClientConverter; @@ -362,9 +363,8 @@ public RegisteredClientDTO registerClient(RegisteredClientDTO request, checkAllowedGrantTypes(request, authentication); cleanupRequestedScopes(client, authentication); - client = clientService.saveNewClient(client); - RegisteredClientDTO response = converter.registrationResponseFromClient(client); + client = clientService.saveNewClient(client); if (isAnonymous(authentication)) { @@ -428,7 +428,6 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO req ClientDetailsEntity newClient = converter.entityFromRegistrationRequest(request); newClient.setId(oldClient.getId()); - newClient.setClientSecret(oldClient.getClientSecret()); newClient.setAccessTokenValiditySeconds(oldClient.getAccessTokenValiditySeconds()); newClient.setIdTokenValiditySeconds(oldClient.getIdTokenValiditySeconds()); newClient.setRefreshTokenValiditySeconds(oldClient.getRefreshTokenValiditySeconds()); @@ -440,6 +439,15 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO req newClient.setReuseRefreshToken(oldClient.isReuseRefreshToken()); newClient.setActive(oldClient.isActive()); + if (NONE.equals(newClient.getTokenEndpointAuthMethod())) { + newClient.setClientSecret(null); + } else if (isNull(request.getClientSecret()) && isNull(oldClient.getClientSecret())) { + newClient.setClientSecret(defaultsService.generateClientSecret()); + } else { + // Direct updates are disabled. Changes must be made via secret reset process + newClient.setClientSecret(oldClient.getClientSecret()); + } + if (registrationProperties.isAdminOnlyCustomScopes() && !accountUtils.isAdmin(authentication)) { removeCustomScopes(newClient); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java index b28e54efd4..bc96b87e86 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java @@ -15,6 +15,8 @@ */ package it.infn.mw.iam.api.client.service; +import static java.util.Objects.isNull; + import java.time.Clock; import java.util.Date; import java.util.Optional; @@ -37,6 +39,7 @@ import it.infn.mw.iam.persistence.repository.client.ClientSpecs; import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; import it.infn.mw.iam.persistence.repository.client.IamClientRepository; +import it.infn.mw.iam.util.IamClientSecretEncoder; @Service @Transactional @@ -53,7 +56,8 @@ public class DefaultClientService implements ClientService { private OAuth2TokenEntityService tokenService; public DefaultClientService(Clock clock, IamClientRepository clientRepo, - IamAccountClientRepository accountClientRepo, ApplicationEventPublisher eventPublisher, OAuth2TokenEntityService tokenService) { + IamAccountClientRepository accountClientRepo, ApplicationEventPublisher eventPublisher, + OAuth2TokenEntityService tokenService) { this.clock = clock; this.clientRepo = clientRepo; this.accountClientRepo = accountClientRepo; @@ -65,6 +69,9 @@ public DefaultClientService(Clock clock, IamClientRepository clientRepo, public ClientDetailsEntity saveNewClient(ClientDetailsEntity client) { client.setCreatedAt(Date.from(clock.instant())); eventPublisher.publishEvent(new ClientCreatedEvent(this, client)); + if (!isNull(client.getClientSecret())) { + client.setClientSecret(new IamClientSecretEncoder().encode(client.getClientSecret())); + } return clientRepo.save(client); } @@ -83,7 +90,7 @@ private Supplier newAccountClient(IamAccount owner, @Override public ClientDetailsEntity linkClientToAccount(ClientDetailsEntity client, IamAccount owner) { IamAccountClient ac = accountClientRepo.findByAccountAndClient(owner, client) - .orElseGet(newAccountClient(owner, client)); + .orElseGet(newAccountClient(owner, client)); return ac.getClient(); } @@ -103,7 +110,8 @@ public ClientDetailsEntity updateClient(ClientDetailsEntity client) { } @Override - public ClientDetailsEntity updateClientStatus(ClientDetailsEntity client, boolean status, String userId) { + public ClientDetailsEntity updateClientStatus(ClientDetailsEntity client, boolean status, + String userId) { client.setActive(status); client.setStatusChangedBy(userId); client.setStatusChangedOn(Date.from(clock.instant())); @@ -124,7 +132,7 @@ public Optional findClientByClientIdAndAccount(String clien if (maybeClient.isPresent()) { return accountClientRepo.findByAccountAndClientId(account, maybeClient.get().getId()) - .map(IamAccountClient::getClient); + .map(IamAccountClient::getClient); } return Optional.empty(); @@ -146,12 +154,12 @@ private boolean isValidAccessToken(OAuth2AccessTokenEntity a) { private void deleteTokensByClient(ClientDetailsEntity client) { // delete all valid access tokens (exclude registration and resource tokens) tokenService.getAccessTokensForClient(client) - .stream() - .filter(this::isValidAccessToken) - .forEach(at -> tokenService.revokeAccessToken(at)); + .stream() + .filter(this::isValidAccessToken) + .forEach(at -> tokenService.revokeAccessToken(at)); // delete all valid refresh tokens tokenService.getRefreshTokensForClient(client) - .forEach(rt -> tokenService.revokeRefreshToken(rt)); + .forEach(rt -> tokenService.revokeRefreshToken(rt)); } @Override diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamApiSecurityConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamApiSecurityConfig.java index b5289d7be1..a34314a74f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamApiSecurityConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamApiSecurityConfig.java @@ -31,7 +31,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter; import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; @@ -42,6 +41,7 @@ import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.config.security.IamWebSecurityConfig.UserLoginConfig; import it.infn.mw.iam.core.oauth.FormClientCredentialsAuthenticationFilter; +import it.infn.mw.iam.util.IamClientSecretEncoder; @SuppressWarnings("deprecation") @Configuration @@ -66,7 +66,7 @@ public static class IamProxyCertificateApiConfig extends WebSecurityConfigurerAd @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) - .passwordEncoder(NoOpPasswordEncoder.getInstance()); + .passwordEncoder(new IamClientSecretEncoder()); } @Override diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamTokenEndointSecurityConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamTokenEndointSecurityConfig.java index 37c8cb621d..f2b9e18cea 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamTokenEndointSecurityConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/IamTokenEndointSecurityConfig.java @@ -32,7 +32,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter; import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; @@ -43,6 +42,7 @@ import it.infn.mw.iam.config.IamProperties; import it.infn.mw.iam.core.client.ClientUserDetailsService; import it.infn.mw.iam.core.oauth.assertion.IAMJWTBearerAuthenticationProvider; +import it.infn.mw.iam.util.IamClientSecretEncoder; @SuppressWarnings("deprecation") @Configuration @@ -71,7 +71,7 @@ public class IamTokenEndointSecurityConfig extends WebSecurityConfigurerAdapter protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) - .passwordEncoder(NoOpPasswordEncoder.getInstance()); + .passwordEncoder(new IamClientSecretEncoder()); } @Bean diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/MitreSecurityConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/MitreSecurityConfig.java index d84606927a..81bdc4c2c1 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/security/MitreSecurityConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/security/MitreSecurityConfig.java @@ -28,7 +28,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter; import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter; import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; @@ -37,6 +36,7 @@ import org.springframework.security.web.context.SecurityContextPersistenceFilter; import it.infn.mw.iam.api.client.registration.ClientRegistrationApiController; +import it.infn.mw.iam.util.IamClientSecretEncoder; @SuppressWarnings("deprecation") @Configuration @@ -185,7 +185,7 @@ public static class IntrospectEndpointAuthorizationConfig extends WebSecurityCon protected void configure(final AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) - .passwordEncoder(NoOpPasswordEncoder.getInstance()); + .passwordEncoder(new IamClientSecretEncoder()); } @Override @@ -227,7 +227,7 @@ public static class RevokeEndpointAuthorizationConfig extends WebSecurityConfigu protected void configure(final AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) - .passwordEncoder(NoOpPasswordEncoder.getInstance()); + .passwordEncoder(new IamClientSecretEncoder()); } private ClientCredentialsTokenEndpointFilter clientCredentialsEndpointFilter() diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamMethodSecurityExpressionHandler.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamMethodSecurityExpressionHandler.java index 87af6e5429..b3817b2c06 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamMethodSecurityExpressionHandler.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamMethodSecurityExpressionHandler.java @@ -24,6 +24,8 @@ import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.api.requests.GroupRequestUtils; import it.infn.mw.iam.core.userinfo.OAuth2AuthenticationScopeResolver; +import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; +import it.infn.mw.iam.persistence.repository.client.IamClientRepository; @SuppressWarnings("deprecation") @Component @@ -32,12 +34,17 @@ public class IamMethodSecurityExpressionHandler extends OAuth2MethodSecurityExpr private final AccountUtils accountUtils; private final GroupRequestUtils groupRequestUtils; private final OAuth2AuthenticationScopeResolver scopeResolver; + private final IamAccountClientRepository accountClientRepo; + private final IamClientRepository clientRepo; public IamMethodSecurityExpressionHandler(AccountUtils accountUtils, - GroupRequestUtils groupRequestUtils, OAuth2AuthenticationScopeResolver scopeResolver) { + GroupRequestUtils groupRequestUtils, OAuth2AuthenticationScopeResolver scopeResolver, + IamAccountClientRepository accountClientRepo, IamClientRepository clientRepo) { this.accountUtils = accountUtils; this.groupRequestUtils = groupRequestUtils; this.scopeResolver = scopeResolver; + this.accountClientRepo = accountClientRepo; + this.clientRepo = clientRepo; } @Override @@ -46,7 +53,7 @@ public StandardEvaluationContext createEvaluationContextInternal(Authentication StandardEvaluationContext ec = super.createEvaluationContextInternal(authentication, mi); ec.setVariable("iam", new IamSecurityExpressionMethods(authentication, accountUtils, - groupRequestUtils, scopeResolver)); + groupRequestUtils, scopeResolver, accountClientRepo, clientRepo)); return ec; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamSecurityExpressionMethods.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamSecurityExpressionMethods.java index c859d17715..ddf4d4cbfd 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamSecurityExpressionMethods.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamSecurityExpressionMethods.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Optional; +import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -31,6 +32,8 @@ import it.infn.mw.iam.core.userinfo.OAuth2AuthenticationScopeResolver; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamGroupRequest; +import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; +import it.infn.mw.iam.persistence.repository.client.IamClientRepository; @SuppressWarnings("deprecation") public class IamSecurityExpressionMethods { @@ -42,13 +45,18 @@ public class IamSecurityExpressionMethods { private final AccountUtils accountUtils; private final GroupRequestUtils groupRequestUtils; private final OAuth2AuthenticationScopeResolver scopeResolver; + private final IamAccountClientRepository accountClientRepo; + private final IamClientRepository clientRepo; public IamSecurityExpressionMethods(Authentication authentication, AccountUtils accountUtils, - GroupRequestUtils groupRequestUtils, OAuth2AuthenticationScopeResolver scopeResolver) { + GroupRequestUtils groupRequestUtils, OAuth2AuthenticationScopeResolver scopeResolver, + IamAccountClientRepository accountClientRepo, IamClientRepository clientRepo) { this.authentication = authentication; this.accountUtils = accountUtils; this.groupRequestUtils = groupRequestUtils; this.scopeResolver = scopeResolver; + this.accountClientRepo = accountClientRepo; + this.clientRepo = clientRepo; } public boolean isExternallyAuthenticatedWithIssuer(String issuer) { @@ -153,4 +161,10 @@ public boolean hasDashboardRole(Role role) { public boolean hasAdminOrGMDashboardRoleOfGroup(String gid) { return (hasDashboardRole(Role.ROLE_ADMIN) || isGroupManager(gid)); } + + public boolean isClientOwner(String clientId) { + Optional owner = accountUtils.getAuthenticatedUserAccount(); + Optional client = clientRepo.findByClientId(clientId); + return owner.isPresent() && client.isPresent() && accountClientRepo.findByAccountAndClient(owner.get(), client.get()).isPresent(); + } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamWebSecurityExpressionHandler.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamWebSecurityExpressionHandler.java index bfc63a708c..fd42c29e04 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamWebSecurityExpressionHandler.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/expression/IamWebSecurityExpressionHandler.java @@ -24,6 +24,8 @@ import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.api.requests.GroupRequestUtils; import it.infn.mw.iam.core.userinfo.OAuth2AuthenticationScopeResolver; +import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; +import it.infn.mw.iam.persistence.repository.client.IamClientRepository; @SuppressWarnings("deprecation") @Component @@ -32,12 +34,17 @@ public class IamWebSecurityExpressionHandler extends OAuth2WebSecurityExpression private final AccountUtils accountUtils; private final GroupRequestUtils groupRequestUtils; private final OAuth2AuthenticationScopeResolver scopeResolver; + private final IamAccountClientRepository accountClientRepo; + private final IamClientRepository clientRepo; public IamWebSecurityExpressionHandler(AccountUtils accountUtils, - GroupRequestUtils groupRequestUtils, OAuth2AuthenticationScopeResolver scopeResolver) { + GroupRequestUtils groupRequestUtils, OAuth2AuthenticationScopeResolver scopeResolver, + IamAccountClientRepository accountClientRepo, IamClientRepository clientRepo) { this.accountUtils = accountUtils; this.groupRequestUtils = groupRequestUtils; this.scopeResolver = scopeResolver; + this.accountClientRepo = accountClientRepo; + this.clientRepo = clientRepo; } @Override @@ -47,7 +54,7 @@ public StandardEvaluationContext createEvaluationContextInternal(Authentication StandardEvaluationContext ec = super.createEvaluationContextInternal(authentication, invocation); ec.setVariable("iam", new IamSecurityExpressionMethods(authentication, accountUtils, - groupRequestUtils, scopeResolver)); + groupRequestUtils, scopeResolver, accountClientRepo, clientRepo)); return ec; } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/util/IamBcryptUtil.java b/iam-login-service/src/main/java/it/infn/mw/iam/util/IamBcryptUtil.java deleted file mode 100644 index 6396c1c01b..0000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/util/IamBcryptUtil.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package it.infn.mw.iam.util; - -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; - -/** - * A simple util to quickly get a password bcrypt-encoded - * - */ -public class IamBcryptUtil { - - public static void main(String[] args) { - - if (args.length == 0) { - System.err.println("Please provide the password to encode as an argument"); - System.exit(1); - } - - BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); - - System.out.println(encoder.encode(args[0])); - } - -} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/util/IamClientSecretEncoder.java b/iam-login-service/src/main/java/it/infn/mw/iam/util/IamClientSecretEncoder.java new file mode 100644 index 0000000000..95e52ed10b --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/util/IamClientSecretEncoder.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package it.infn.mw.iam.util; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +/** + * A simple util to quickly get a password bcrypt-encoded + * + */ +public class IamClientSecretEncoder implements PasswordEncoder { + + static final int DEFAULT_ROUND = 12; + + BCryptPasswordEncoder bcryptEncoder = new BCryptPasswordEncoder(DEFAULT_ROUND); + + @Override + public String encode(CharSequence rawPassword) { + if (rawPassword.isEmpty()) { + return rawPassword.toString(); + } + return bcryptEncoder.encode(rawPassword); + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + if (rawPassword == null) { + throw new IllegalArgumentException("rawPassword cannot be null"); + } + if (encodedPassword == null) { + throw new IllegalArgumentException("encodedPassword cannot be null"); + } + if (rawPassword.isEmpty() && encodedPassword.isEmpty()) { + return true; + } + return bcryptEncoder.matches(rawPassword, encodedPassword); + } + +} diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js index 9af47294c5..11d814a398 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js @@ -16,6 +16,49 @@ (function () { 'use strict'; + function ClientSecretViewController($uibModal, $uibModalInstance, toaster, ClientsService, data) { + var $ctrl = this; + $ctrl.data = data; + $ctrl.isNewClient = data.isNewClient; + $ctrl.newClient = data.client; + $ctrl.secret = $ctrl.newClient.client_secret; + $ctrl.clientId = $ctrl.newClient.client_id; + $ctrl.showSecret = false; + $ctrl.confirmation = true; + + self.clipboardSuccess = clipboardSuccess; + self.clipboardError = clipboardError; + + $ctrl.ok = function() { + $uibModalInstance.close($ctrl.selected); + }; + + $ctrl.closeModal = function() { + $uibModalInstance.dismiss('cancel'); + }; + + $ctrl.toggleSecretVisibility = function() { + $ctrl.showSecret = !$ctrl.showSecret; + }; + + function clipboardError(event) { + toaster.pop({ + type: 'error', + body: 'Could not copy secret to clipboard!' + }); + } + + function clipboardSuccess(event, source) { + toaster.pop({ + type: 'success', + body: 'Secret copied to clipboard!' + }); + event.clearSelection(); + if (source === 'secret') { + toggleSecretVisibility(); + } + } + }; function ClientController(ClientsService, FindService, toaster, $uibModal, $location) { var self = this; @@ -61,7 +104,6 @@ function saveClient() { - function handleSuccess(res) { self.client = res; self.clientVal = angular.copy(self.client); @@ -93,7 +135,36 @@ type: 'success', body: 'Client saved!' }); - $location.path('/clients'); + + const isNewClientPublic = self.clientVal.token_endpoint_auth_method === 'none'; + + if (isNewClientPublic) { + $location.path('/clients'); + return; + } + + var modalSecret = $uibModal.open({ + templateUrl: '/resources/iam/apps/dashboard-app/components/clients/client/newclientsecretshow/newclientsecretshow.component.html', + controller: ClientSecretViewController, + controllerAs: '$ctrl', + resolve: { + data: { + client: res, + title: "New client credential details", + message: "Save this client credential on safe before press Confirm button", + isNewClient: true, + } + } + }); + + modalSecret.result + .then(() => {$location.path('/clients');}) + .catch(() => { + toaster.pop({ + type: 'error', + body: errorMsg + }); + }); }).catch(handleError); } else { diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientauth/clientauth.component.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientauth/clientauth.component.html index 1f7e75db1a..e221764b5a 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientauth/clientauth.component.html +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientauth/clientauth.component.html @@ -56,7 +56,6 @@ -

diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.component.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.component.html index fcd1187cc7..24d301a2eb 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.component.html +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.component.html @@ -15,52 +15,20 @@ limitations under the License. --> -
-
- - -
- - - - - - - - -
-

- The secret will be generated when the client is saved. -

-
-
- -
-
- -
-
- -

- Not defined. -

+
+
+
+
+
-
-
+

- Registration access token provides management access to the - client. + Registration access token provides management access to the client.

Regenerate registration - access token \ No newline at end of file + ng-if="!$ctrl.newClient && !$ctrl.isLimited()">Regenerate registration access token + + + + diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.component.js index 42ec45c4e8..9bc7b891e0 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.component.js @@ -16,16 +16,50 @@ (function () { 'use strict'; - function ClientSecretController(ModalService, toaster, ClientsService) { + function ModalClientSecretController($rootScope, $scope, $uibModal, $uibModalInstance, ClientsService, toaster, client) { var self = this; self.showSecret = false; - self.toggleSecretVisibility = toggleSecretVisibility; + // self.toggleSecretVisibility = toggleSecretVisibility; self.clipboardSuccess = clipboardSuccess; self.clipboardError = clipboardError; - self.rotateClientSecret = rotateClientSecret; - self.isLimited = isLimited; - self.rotateClientRat = rotateClientRat; + self.confirmation = true; + self.clientId = client.client_id; + self.clientName = client.client_name; + self.isNewClient = !!self.clientId; + self.parent = parent; + self.showSecret = false; + + self.closeModal = function () { + self.isModalOpen = false; + $uibModalInstance.dismiss('Dismissed'); + }; + + self.closeModal = function () { + self.isModalOpen = false; + $uibModalInstance.close(); + }; + + self.toggleSecretVisibility = function() { + self.showSecret = !self.showSecret; + }; + + self.confirmRequestNewSecret = function () { + self.confirmation = true; + var results = ClientsService.rotateClientSecret(client.client_id).then(res => { + self.newSecret = res.client_secret; + toaster.pop({ + type: 'success', + body: 'Registration access token rotated for client ' + self.clientName + }); + }).catch(error => { + console.error(error); + toaster.pop({ + type: 'error', + body: 'Could not rotate secret for client ' + self.clientName + }); + }); + } function clipboardError(event) { toaster.pop({ @@ -34,10 +68,6 @@ }); } - function isLimited() { - return self.limited === 'true' | self.limited; - } - function clipboardSuccess(event, source) { toaster.pop({ type: 'success', @@ -48,9 +78,31 @@ toggleSecretVisibility(); } } + self.confirmRequestNewSecret(); + } - function toggleSecretVisibility() { - self.showSecret = !self.showSecret; + function ClientSecretController($uibModal, toaster, ClientsService) { + var self = this; + + self.isLimited = isLimited; + self.rotateClientRat = rotateClientRat; + self.openModalRequestClientSecret = function () { + self.isModalOpen = true; + + var modalSecret = $uibModal.open({ + templateUrl: '/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.dialog.html', + controller: ModalClientSecretController, + controllerAs: '$ctrl', + resolve: { + client: () => { return self.client } + } + }); + + modalSecret.result.then(self.handleSuccess); + }; + + function isLimited() { + return self.limited === 'true' | self.limited; } function rotateClientRat() { @@ -70,37 +122,6 @@ } } - function rotateClientSecret() { - - var modalOptions = { - closeButtonText: 'Cancel', - actionButtonText: 'Confirm Change', - headerText: 'Regenerate Client Secret', - bodyText: - `Are you sure you want to change the secret of this client: ` + self.client.client_name+ ` ?` - }; - - ModalService.showModal({}, modalOptions) - .then( - function() { - ClientsService.rotateClientSecret(self.client.client_id).then(res => { - self.client = res; - toaster.pop({ - type: 'success', - body: 'Secret rotated for client ' + self.client.client_name - }); - }).catch(res => { - toaster.pop({ - type: 'error', - body: 'Could not rotate secret for client ' + self.client.client_name - }); - }); - } - ).catch(function(error) { - console.info("Cancel Regenerate Client Secret"); - }); - } - self.$onInit = function () { console.debug('ClientSecretController.self', self); }; @@ -120,7 +141,7 @@ newClient: "<", limited: '@' }, - controller: ['ModalService', 'toaster', 'ClientsService', ClientSecretController], + controller: ['$uibModal', 'toaster', 'ClientsService', ClientSecretController], controllerAs: '$ctrl' }; } diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.dialog.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.dialog.html new file mode 100644 index 0000000000..9b6b66a66b --- /dev/null +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/clientsecret/clientsecret.dialog.html @@ -0,0 +1,55 @@ + + + + diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/newclientsecretshow/clientsecretview.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/newclientsecretshow/clientsecretview.component.js new file mode 100644 index 0000000000..2a6a6d5f38 --- /dev/null +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/newclientsecretshow/clientsecretview.component.js @@ -0,0 +1,73 @@ +/* + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function () { + 'use strict'; + + function ClientSecretViewController($uibModal, toaster, ClientsService, data) { + + var $ctrl = this; + + $ctrl.data = data; + $ctrl.newClient = !!data.client.clientSecret ; + $ctrl.client = data.client + + self.clipboardSuccess = clipboardSuccess; + self.clipboardError = clipboardError; + + $ctrl.ok = function() { + $uibModalInstance.close($ctrl.selected); + }; + + $ctrl.closeModal = function() { + $uibModalInstance.dismiss('cancel'); + }; + + function clipboardError(event) { + toaster.pop({ + type: 'error', + body: 'Could not copy secret to clipboard!' + }); + } + + function clipboardSuccess(event, source) { + toaster.pop({ + type: 'success', + body: 'Secret copied to clipboard!' + }); + event.clearSelection(); + if (source === 'secret') { + toggleSecretVisibility(); + } + } + }; + + angular.module('dashboardApp') + .component('ClientSecretView', ClientSecretView()); + + function ClientSecretView() { + return { + templateUrl: "/resources/iam/apps/dashboard-app/components/clients/client/newclientsecretshow/clientsecretview.component.html", + bindings: { + client: "=", + newClient: "<", + limited: '@' + }, + controller: ['$uibModal', 'toaster', 'ClientsService', 'data', ClientSecretViewController], + controllerAs: '$ctrl' + }; + } + +}()); \ No newline at end of file diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/newclientsecretshow/newclientsecretshow.component.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/newclientsecretshow/newclientsecretshow.component.html new file mode 100644 index 0000000000..793467fc50 --- /dev/null +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/newclientsecretshow/newclientsecretshow.component.html @@ -0,0 +1,65 @@ + + + + \ No newline at end of file diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclient/myclient.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclient/myclient.component.js index 9d2df3592c..a4629a37d6 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclient/myclient.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclient/myclient.component.js @@ -16,6 +16,50 @@ (function () { 'use strict'; + function ClientSecretViewController($uibModal, $uibModalInstance, toaster, ClientsService, data) { + var $ctrl = this; + $ctrl.data = data; + $ctrl.isNewClient = data.isNewClient; + $ctrl.newClient = data.client; + $ctrl.secret = $ctrl.newClient.client_secret; + $ctrl.clientId = $ctrl.newClient.client_id; + $ctrl.showSecret = false; + $ctrl.confirmation = true; + + self.clipboardSuccess = clipboardSuccess; + self.clipboardError = clipboardError; + + $ctrl.ok = function () { + $uibModalInstance.close($ctrl.selected); + }; + + $ctrl.closeModal = function () { + $uibModalInstance.dismiss('cancel'); + }; + + $ctrl.toggleSecretVisibility = function () { + $ctrl.showSecret = !$ctrl.showSecret; + }; + + function clipboardError(event) { + toaster.pop({ + type: 'error', + body: 'Could not copy secret to clipboard!' + }); + } + + function clipboardSuccess(event, source) { + toaster.pop({ + type: 'success', + body: 'Secret copied to clipboard!' + }); + event.clearSelection(); + if (source === 'secret') { + toggleSecretVisibility(); + } + } + }; + function MyClientController($location, ClientRegistrationService, toaster, $uibModal) { var self = this; @@ -54,7 +98,29 @@ type: 'success', body: 'Client saved!' }); - return res; + + var res = () => { + var modalInstance = $uibModal.open({ + templateUrl: 'clientsecret-view.content.html', + controller: 'ModalClientSecretViewController', + resolve: { + data: function () { + return { + title: '', + message: '', + clientDetail: res, + }; + } + } + }); + + modalInstance.result.then(function (selectedItem) { + $ctrl.selected = selectedItem; + }, function () { + console.log('Dialog dismissed at: ' + new Date()); + }); + return res; + } } function handleError(res) { @@ -74,7 +140,35 @@ function registerClient() { return ClientRegistrationService.registerClient(self.clientVal).then(res => { handleSuccess(res); - $location.path('/home/clients'); + const isNewClientPublic = self.clientVal.token_endpoint_auth_method === 'none'; + + if (isNewClientPublic) { + $location.path('/home/clients'); + return res; + } + + var modalSecret = $uibModal.open({ + templateUrl: '/resources/iam/apps/dashboard-app/components/clients/client/newclientsecretshow/newclientsecretshow.component.html', + controller: ClientSecretViewController, + controllerAs: '$ctrl', + resolve: { + data: { + client: res, + title: "New client credential details", + message: "Save this client credential on safe before press Confirm button", + isNewClient: true, + } + } + }); + modalSecret.result + .then(() => { $location.path('/home/clients'); }) + .catch(() => { + toaster.pop({ + type: 'error', + body: errorMsg + }); + }); + return res; }).catch(handleError); } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java index 0f791d6e0e..e335ef2a41 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java @@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasSize; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -34,6 +35,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.test.context.support.WithAnonymousUser; @@ -45,6 +47,8 @@ import it.infn.mw.iam.IamLoginService; import it.infn.mw.iam.api.client.management.ClientManagementAPIController; +import it.infn.mw.iam.api.client.registration.ClientRegistrationApiController; +import it.infn.mw.iam.api.client.util.ClientSuppliers; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.persistence.repository.client.IamClientRepository; import it.infn.mw.iam.test.api.TestSupport; @@ -53,7 +57,6 @@ import it.infn.mw.iam.test.util.WithMockOAuthUser; import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; import it.infn.mw.iam.test.util.oauth.MockOAuth2Filter; -import org.mitre.oauth2.model.ClientDetailsEntity; @IamMockMvcIntegrationTest @SpringBootTest(classes = {IamLoginService.class, CoreControllerTestSupport.class}) @@ -211,10 +214,13 @@ void setTokenLifetimesWorks() throws Exception { assertEquals(600, client.getDeviceCodeValiditySeconds()); Optional clientDB = clientRepo.findByClientId(client.getClientId()); - assertEquals(client.getAccessTokenValiditySeconds(), clientDB.get().getAccessTokenValiditySeconds()); - assertEquals(client.getRefreshTokenValiditySeconds(), clientDB.get().getRefreshTokenValiditySeconds()); + assertEquals(client.getAccessTokenValiditySeconds(), + clientDB.get().getAccessTokenValiditySeconds()); + assertEquals(client.getRefreshTokenValiditySeconds(), + clientDB.get().getRefreshTokenValiditySeconds()); assertEquals(client.getIdTokenValiditySeconds(), clientDB.get().getIdTokenValiditySeconds()); - assertEquals(client.getDeviceCodeValiditySeconds(), clientDB.get().getDeviceCodeValiditySeconds()); + assertEquals(client.getDeviceCodeValiditySeconds(), + clientDB.get().getDeviceCodeValiditySeconds()); clientJson = ClientJsonStringBuilder.builder() .scopes("openid") @@ -235,10 +241,13 @@ void setTokenLifetimesWorks() throws Exception { assertEquals(0, client.getRefreshTokenValiditySeconds()); clientDB = clientRepo.findByClientId(client.getClientId()); - assertEquals(client.getAccessTokenValiditySeconds(), clientDB.get().getAccessTokenValiditySeconds()); - assertEquals(client.getRefreshTokenValiditySeconds(), clientDB.get().getRefreshTokenValiditySeconds()); + assertEquals(client.getAccessTokenValiditySeconds(), + clientDB.get().getAccessTokenValiditySeconds()); + assertEquals(client.getRefreshTokenValiditySeconds(), + clientDB.get().getRefreshTokenValiditySeconds()); assertEquals(client.getIdTokenValiditySeconds(), clientDB.get().getIdTokenValiditySeconds()); - assertEquals(client.getDeviceCodeValiditySeconds(), clientDB.get().getDeviceCodeValiditySeconds()); + assertEquals(client.getDeviceCodeValiditySeconds(), + clientDB.get().getDeviceCodeValiditySeconds()); clientJson = ClientJsonStringBuilder.builder() .scopes("openid") @@ -259,10 +268,13 @@ void setTokenLifetimesWorks() throws Exception { assertEquals(10, client.getRefreshTokenValiditySeconds()); clientDB = clientRepo.findByClientId(client.getClientId()); - assertEquals(client.getAccessTokenValiditySeconds(), clientDB.get().getAccessTokenValiditySeconds()); - assertEquals(client.getRefreshTokenValiditySeconds(), clientDB.get().getRefreshTokenValiditySeconds()); + assertEquals(client.getAccessTokenValiditySeconds(), + clientDB.get().getAccessTokenValiditySeconds()); + assertEquals(client.getRefreshTokenValiditySeconds(), + clientDB.get().getRefreshTokenValiditySeconds()); assertEquals(client.getIdTokenValiditySeconds(), clientDB.get().getIdTokenValiditySeconds()); - assertEquals(client.getDeviceCodeValiditySeconds(), clientDB.get().getDeviceCodeValiditySeconds()); + assertEquals(client.getDeviceCodeValiditySeconds(), + clientDB.get().getDeviceCodeValiditySeconds()); } @@ -298,12 +310,12 @@ void negativeRefreshTokenLifetimesSetToInfinite() throws Exception { @WithMockUser(username = "admin", roles = {"ADMIN", "USER"}) void setClientEnableWorks() throws Exception { - mvc.perform(get(ClientManagementAPIController.ENDPOINT + "/client")) + mvc.perform(get(ClientManagementAPIController.ENDPOINT + "/client")) .andExpect(OK) .andExpect(jsonPath("$.active").value(true)); - mvc.perform(patch(ClientManagementAPIController.ENDPOINT + "/{clientId}/enable", "client") - ).andExpect(OK); + mvc.perform(patch(ClientManagementAPIController.ENDPOINT + "/{clientId}/enable", "client")) + .andExpect(OK); mvc.perform(get(ClientManagementAPIController.ENDPOINT + "/client")) .andExpect(OK) @@ -314,15 +326,93 @@ void setClientEnableWorks() throws Exception { @WithMockUser(username = "admin", roles = {"ADMIN", "USER"}) void setClientDisableWorks() throws Exception { - mvc.perform(get(ClientManagementAPIController.ENDPOINT + "/client")) + mvc.perform(get(ClientManagementAPIController.ENDPOINT + "/client")) .andExpect(OK) .andExpect(jsonPath("$.active").value(true)); - mvc.perform(patch(ClientManagementAPIController.ENDPOINT + "/{clientId}/disable", "client") - ).andExpect(OK); + mvc.perform(patch(ClientManagementAPIController.ENDPOINT + "/{clientId}/disable", "client")) + .andExpect(OK); mvc.perform(get(ClientManagementAPIController.ENDPOINT + "/client")) .andExpect(OK) .andExpect(jsonPath("$.active").value(false)); } + + @Test + @WithMockUser(username = "test", roles = {"USER"}) + void secretRotationWorksForClientOwner() throws Exception { + + String clientJson = ClientJsonStringBuilder.builder().scopes("openid").build(); + + String responseJson = mvc + .perform(post(ClientRegistrationApiController.ENDPOINT).contentType(APPLICATION_JSON) + .content(clientJson)) + .andExpect(CREATED) + .andReturn() + .getResponse() + .getContentAsString(); + + RegisteredClientDTO client = mapper.readValue(responseJson, RegisteredClientDTO.class); + String clientSecret = client.getClientSecret(); + + final String url = + String.format("%s/%s/secret", ClientManagementAPIController.ENDPOINT, client.getClientId()); + + responseJson = mvc.perform(post(url)).andReturn().getResponse().getContentAsString(); + client = mapper.readValue(responseJson, RegisteredClientDTO.class); + String clientSecretRenewed = client.getClientSecret(); + assertNotEquals(clientSecret, clientSecretRenewed); + } + + @Test + @WithMockUser(username = "admin", roles = {"ADMIN", "USER"}) + void secretRotationWorksForAdminUser() throws Exception { + + String clientJson = ClientJsonStringBuilder.builder().scopes("openid").build(); + + String responseJson = mvc + .perform(post(ClientManagementAPIController.ENDPOINT).contentType(APPLICATION_JSON) + .content(clientJson)) + .andExpect(CREATED) + .andReturn() + .getResponse() + .getContentAsString(); + + RegisteredClientDTO client = mapper.readValue(responseJson, RegisteredClientDTO.class); + String clientSecret = client.getClientSecret(); + + final String url = + String.format("%s/%s/secret", ClientManagementAPIController.ENDPOINT, client.getClientId()); + + responseJson = mvc.perform(post(url)).andReturn().getResponse().getContentAsString(); + client = mapper.readValue(responseJson, RegisteredClientDTO.class); + String clientSecretRenewed = client.getClientSecret(); + assertNotEquals(clientSecret, clientSecretRenewed); + } + + private void assertSecretRotationForbidden(String clientId) throws Exception { + ClientDetailsEntity client = + clientRepo.findByClientId(clientId).orElseThrow(ClientSuppliers.clientNotFound(clientId)); + + final String url = + String.format("%s/%s/secret", ClientManagementAPIController.ENDPOINT, client.getClientId()); + + mvc.perform(post(url)) + .andExpect(FORBIDDEN) + .andExpect(jsonPath("$.error", containsString("access_denied"))); + } + + @Test + @WithMockUser(username = "test", roles = {"USER"}) + void secretRotationFailsForOtherUsers() throws Exception { + + assertSecretRotationForbidden("client"); + } + + @Test + @WithMockUser(username = "non-existent-user", roles = {"USER"}) + void secretRotationFailsWhenUserNotFound() throws Exception { + + assertSecretRotationForbidden("client-cred"); + } } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/RegistrationAccessTokenTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/RegistrationAccessTokenTests.java index 331a821935..6e8a08c908 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/RegistrationAccessTokenTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/RegistrationAccessTokenTests.java @@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTParser; @@ -44,6 +45,7 @@ import it.infn.mw.iam.test.api.TestSupport; import it.infn.mw.iam.test.oauth.client_registration.ClientRegistrationTestSupport.ClientJsonStringBuilder; import it.infn.mw.iam.test.util.annotation.IamRandomPortIntegrationTest; +import it.infn.mw.iam.util.IamClientSecretEncoder; @IamRandomPortIntegrationTest public class RegistrationAccessTokenTests extends TestSupport { @@ -110,7 +112,7 @@ public void testRatWorkAsExpected() throws ParseException { .body() .as(RegisteredClientDTO.class); - assertThat(getResponse.getClientSecret(), is(registerResponse.getClientSecret())); + assertThat(new IamClientSecretEncoder().matches(registerResponse.getClientSecret(), getResponse.getClientSecret()), is(true)); assertThat(getResponse.getRegistrationAccessToken(), nullValue()); RegisteredClientDTO rotatedRatClient = diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/util/IamSecurityExpressionsTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamSecurityExpressionMethodsTests.java similarity index 51% rename from iam-login-service/src/test/java/it/infn/mw/iam/test/util/IamSecurityExpressionsTests.java rename to iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamSecurityExpressionMethodsTests.java index d53159c4e2..55476530dc 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/util/IamSecurityExpressionsTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamSecurityExpressionMethodsTests.java @@ -13,37 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package it.infn.mw.iam.test.util; +package it.infn.mw.iam.test.core; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; + +import java.util.Optional; import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.service.ClientDetailsEntityService; +import org.mockito.Mock; +import org.mockito.Spy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit4.SpringRunner; import it.infn.mw.iam.IamLoginService; import it.infn.mw.iam.api.account.AccountUtils; +import it.infn.mw.iam.api.client.service.ClientService; import it.infn.mw.iam.api.requests.GroupRequestUtils; import it.infn.mw.iam.api.requests.model.GroupRequestDto; import it.infn.mw.iam.core.expression.IamSecurityExpressionMethods; import it.infn.mw.iam.core.userinfo.OAuth2AuthenticationScopeResolver; +import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamGroupRequestRepository; +import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository; +import it.infn.mw.iam.persistence.repository.client.IamClientRepository; import it.infn.mw.iam.test.api.requests.GroupRequestsTestUtils; +import it.infn.mw.iam.test.util.oauth.MockOAuth2Request; +@SuppressWarnings("deprecation") @RunWith(SpringRunner.class) @SpringBootTest(classes = {IamLoginService.class}, webEnvironment = WebEnvironment.MOCK) -public class IamSecurityExpressionsTests extends GroupRequestsTestUtils { +public class IamSecurityExpressionMethodsTests extends GroupRequestsTestUtils { - @Autowired - private AccountUtils accountUtils; + public static final String TEST_CLIENT_ID = "client"; @Autowired private GroupRequestUtils groupRequestUtils; @@ -54,18 +68,47 @@ public class IamSecurityExpressionsTests extends GroupRequestsTestUtils { @Autowired private IamGroupRequestRepository repo; + @Autowired + private IamAccountClientRepository accountClientRepo; + + @Autowired + private IamAccountRepository accountRepo; + + @Autowired + private ClientService clientService; + + @Autowired + private ClientDetailsEntityService clientDetailsService; + + @Autowired + AccountUtils accountUtils; + + @Mock + private IamClientRepository clientRepo; + + @Mock + OAuth2Authentication authentication; + + @Spy + MockOAuth2Request oauth2Request = new MockOAuth2Request("clientId", new String[] {"openid", "profile"}); + @After public void destroy() { repo.deleteAll(); + clientService.unlinkClientFromAccount(clientDetailsService.loadClientByClientId(TEST_CLIENT_ID), + accountRepo.findByUsername(TEST_ADMIN).get()); + clientService.unlinkClientFromAccount(clientDetailsService.loadClientByClientId(TEST_CLIENT_ID), + accountRepo.findByUsername("test_200").get()); } - + private IamSecurityExpressionMethods getMethods() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return new IamSecurityExpressionMethods(authentication, accountUtils, groupRequestUtils, scopeResolver); + return new IamSecurityExpressionMethods(authentication, accountUtils, groupRequestUtils, + scopeResolver, accountClientRepo, clientRepo); } @Test - @WithMockUser(roles = { "ADMIN", "USER" }, username = TEST_ADMIN) + @WithMockUser(roles = {"ADMIN", "USER"}, username = TEST_ADMIN) public void testIsAdmin() { assertTrue(getMethods().isAdmin()); assertTrue(getMethods().isUser(TEST_ADMIN_UUID)); @@ -77,7 +120,7 @@ public void testIsAdmin() { } @Test - @WithMockUser(roles = { "USER" }, username = TEST_USERNAME) + @WithMockUser(roles = {"USER"}, username = TEST_USERNAME) public void testIsNotAdmin() { assertFalse(getMethods().isAdmin()); assertTrue(getMethods().isUser(TEST_USERUUID)); @@ -95,4 +138,48 @@ public void testIsNotAdmin() { assertFalse(getMethods().canManageGroupRequest(notMine.getUuid())); assertFalse(getMethods().userCanDeleteGroupRequest(notMine.getUuid())); } + + @Test + @WithMockUser(roles = {"ADMIN", "USER"}) + public void testIsClientOwnerNoAuthenticatedUser() { + assertFalse(getMethods().isClientOwner("client")); + } + + @Test + @WithMockUser(roles = {"ADMIN", "USER"}, username = TEST_ADMIN) + public void testIsClientOwnerIsAdmin() { + ClientDetailsEntity clientTest = clientDetailsService.loadClientByClientId(TEST_CLIENT_ID); + Optional account = accountRepo.findByUsername(TEST_ADMIN); + ClientDetailsEntity clientTestUpdate = clientService.linkClientToAccount(clientTest, account.get()); + + doReturn(Optional.of(clientTestUpdate)).when(clientRepo).findByClientId(TEST_CLIENT_ID); + + assertTrue(getMethods().isClientOwner(TEST_CLIENT_ID)); + } + + @Test + @WithMockUser(roles = {"ADMIN", "USER"}, username = "test_200") + public void testIsClientOwnerIsUser() { + String owner = "test_200"; + ClientDetailsEntity clientTest = clientDetailsService.loadClientByClientId(TEST_CLIENT_ID); + Optional account = accountRepo.findByUsername(owner); + ClientDetailsEntity clientTestUpdate = clientService.linkClientToAccount(clientTest, account.get()); + + doReturn(Optional.of(clientTestUpdate)).when(clientRepo).findByClientId(TEST_CLIENT_ID); + + assertTrue(getMethods().isClientOwner(TEST_CLIENT_ID)); + } + + @Test + @WithMockUser(roles = {"ADMIN", "USER"}, username = TEST_ADMIN) + public void testIsClientOwnerIsNotUser() { + String owner = "test_200"; + ClientDetailsEntity clientTest = clientDetailsService.loadClientByClientId(TEST_CLIENT_ID); + Optional account = accountRepo.findByUsername(owner); + ClientDetailsEntity clientTestUpdate = clientService.linkClientToAccount(clientTest, account.get()); + + doReturn(Optional.of(clientTestUpdate)).when(clientRepo).findByClientId(TEST_CLIENT_ID); + + assertFalse(getMethods().isClientOwner(TEST_CLIENT_ID)); + } } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTestSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTestSupport.java index de26d64797..763de342c6 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTestSupport.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/assertion/JWTBearerClientAuthenticationIntegrationTestSupport.java @@ -24,6 +24,7 @@ import java.util.UUID; import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.oauth2.model.ClientDetailsEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ResourceLoader; @@ -36,13 +37,13 @@ import com.nimbusds.jwt.SignedJWT; import it.infn.mw.iam.core.jwk.IamJWTSigningService; +import it.infn.mw.iam.persistence.repository.client.IamClientRepository; import it.infn.mw.iam.test.oauth.EndpointsTestUtils; import it.infn.mw.iam.util.JWKKeystoreLoader; public class JWTBearerClientAuthenticationIntegrationTestSupport extends EndpointsTestUtils { public static final String CLIENT_ID_SECRET_JWT = "jwt-auth-client_secret_jwt"; - public static final String CLIENT_ID_SECRET_JWT_SECRET = "c8e9eed0-e6e4-4a66-b16e-6f37096356a7"; public static final String TOKEN_ENDPOINT_AUDIENCE = "http://localhost:8080/token"; public static final String TOKEN_ENDPOINT = "/token"; public static final String JWT_BEARER_ASSERTION_TYPE = @@ -55,10 +56,14 @@ public class JWTBearerClientAuthenticationIntegrationTestSupport extends Endpoin @Autowired ResourceLoader loader; + @Autowired + private IamClientRepository clientRepo; + public SignedJWT createSymmetricClientAuthToken(String clientId, Instant expirationTime) throws JOSEException { - JWSSigner signer = new MACSigner(CLIENT_ID_SECRET_JWT_SECRET); + ClientDetailsEntity client = clientRepo.findByClientId(CLIENT_ID_SECRET_JWT).orElseThrow(); + JWSSigner signer = new MACSigner(client.getClientSecret()); JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().subject(clientId) .issuer(clientId) .expirationTime(Date.from(expirationTime)) diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeTests.java index 558faf6892..1cd2520284 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/devicecode/DeviceCodeTests.java @@ -596,7 +596,7 @@ public void deviceCodeWorksForDynamicallyRegisteredClient() assertNotNull(newClient); - String tokenResponse = getTokenResponse(newClient.getClientId(), newClient.getClientSecret(), + String tokenResponse = getTokenResponse(newClient.getClientId(), registrationResponse.getClientSecret(), TEST_USERNAME, TEST_PASSWORD, "openid profile offline_access"); JsonNode tokenResponseJson = mapper.readTree(tokenResponse); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java index 85ad1a3a4b..453c27807b 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java @@ -46,6 +46,7 @@ public class ScopeMatcherNoCacheTests extends EndpointsTestUtils { private static final String CLIENT_ID = "cache-client"; private static final String CLIENT_SECRET = "secret"; + private static final String CLIENT_SECRET_HASH = "$2a$12$WWEtffWdIellMxblYDNEx..nVahwP9ZMuRYyVdYFb.7DECPRaOp1K"; @Autowired private IamClientRepository clientRepo; @@ -53,11 +54,11 @@ public class ScopeMatcherNoCacheTests extends EndpointsTestUtils { @Autowired private CacheManager cacheManager; - private String getAccessTokenForClient(String scopes) throws Exception { + private String getAccessTokenForClient(String scopes, String secret) throws Exception { return new AccessTokenGetter().grantType("client_credentials") .clientId(CLIENT_ID) - .clientSecret(CLIENT_SECRET) + .clientSecret(secret) .scope(scopes) .getAccessTokenValue(); } @@ -74,17 +75,17 @@ public void updatingClientScopesWithNoCache() throws Exception { ClientDetailsEntity client = new ClientDetailsEntity(); client.setClientId(CLIENT_ID); - client.setClientSecret(CLIENT_SECRET); + client.setClientSecret(CLIENT_SECRET_HASH); client.setScope(Sets.newHashSet("openid", "profile", "email")); clientRepo.save(client); try { - JWT token = JWTParser.parse(getAccessTokenForClient("openid profile email")); + JWT token = JWTParser.parse(getAccessTokenForClient("openid profile email", CLIENT_SECRET)); assertThat("scim:read", not(in(token.getJWTClaimsSet().getClaim("scope").toString().split(" ")))); client.setScope(Sets.newHashSet("openid", "profile", "email", "scim:read")); clientRepo.save(client); - token = JWTParser.parse(getAccessTokenForClient("openid profile email scim:read")); + token = JWTParser.parse(getAccessTokenForClient("openid profile email scim:read", CLIENT_SECRET)); assertThat("scim:read", in(token.getJWTClaimsSet().getClaim("scope").toString().split(" "))); } finally { clientRepo.delete(client); diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java index 3f623679a9..60542d7d7b 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java @@ -67,6 +67,7 @@ import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.test.util.annotation.IamNoMvcTest; +import it.infn.mw.iam.util.IamClientSecretEncoder; @IamNoMvcTest @SpringBootTest(classes = {IamLoginService.class, ClientTestConfig.class}, @@ -131,7 +132,7 @@ void testClientRetrieve() { RegisteredClientDTO client = managementService.retrieveClientByClientId("client").orElseThrow(); assertThat(client.getClientId(), is("client")); - assertThat(client.getClientSecret(), is("secret")); + assertThat(new IamClientSecretEncoder().matches("secret", client.getClientSecret()), is(true)); assertThat(client.getGrantTypes(), hasItems(CODE, REDELEGATE, IMPLICIT, REFRESH_TOKEN)); assertThat(client.getScope(), hasItems("openid", "offline_access", "profile", "email", "address", "phone", "read-tasks", "write-tasks", "read:/", "write:/")); diff --git a/iam-persistence/src/main/java/db/migration/mysql/V110__HashClientSecret.java b/iam-persistence/src/main/java/db/migration/mysql/V110__HashClientSecret.java new file mode 100644 index 0000000000..6612a8ca8c --- /dev/null +++ b/iam-persistence/src/main/java/db/migration/mysql/V110__HashClientSecret.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2018 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package db.migration.mysql; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import it.infn.mw.iam.persistence.migrations.BaseFlywayJavaMigrationAdapter; + +public class V110__HashClientSecret extends BaseFlywayJavaMigrationAdapter { + + public static final Logger LOG = LoggerFactory.getLogger(V110__HashClientSecret.class); + + @Override + public void migrate(JdbcTemplate jdbcTemplate) throws DataAccessException { + + final int DEFAULT_ROUND = 12; + + PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(DEFAULT_ROUND); + + LOG.debug("### START MIGRATION V110__HashClientSecret ###"); + + SqlRowSet clientList = + jdbcTemplate.queryForRowSet("SELECT id, client_secret FROM client_details"); + + while (clientList.next()) { + String clientSecret = clientList.getString("client_secret"); + if (clientSecret == null) { + continue; + } + + Long id = clientList.getLong("id"); + String secretHash = passwordEncoder.encode(clientSecret); + + jdbcTemplate.update("UPDATE client_details SET client_secret=? WHERE id=?", secretHash, id); + } + + LOG.debug("### END MIGRATION V110__HashClientSecret ###"); + } + +} diff --git a/iam-persistence/src/main/resources/db/migration/prod/V3___basic_configuration.sql b/iam-persistence/src/main/resources/db/migration/prod/V3___basic_configuration.sql index 7b13ae0014..e638ce0450 100644 --- a/iam-persistence/src/main/resources/db/migration/prod/V3___basic_configuration.sql +++ b/iam-persistence/src/main/resources/db/migration/prod/V3___basic_configuration.sql @@ -24,7 +24,9 @@ INSERT INTO iam_account_authority(account_id, authority_id) VALUES INSERT INTO client_details (id, client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection, token_endpoint_auth_method) VALUES (1, 'client', 'secret', 'Test Client', false, null, 3600, 600, true, 'SECRET_BASIC'); - + +UPDATE client_details SET client_secret = '$2a$12$P6AOS2.9DS6L.VaI1qjWnuVFbqUlpU449WDpISytV23H2ANUq0Gtu' WHERE client_secret = 'secret'; + INSERT INTO client_scope (owner_id, scope) VALUES (1, 'openid'), (1, 'profile'), diff --git a/iam-persistence/src/main/resources/db/migration/test/V100000___test_data.sql b/iam-persistence/src/main/resources/db/migration/test/V100000___test_data.sql index c9adfd210b..9ab0244226 100644 --- a/iam-persistence/src/main/resources/db/migration/test/V100000___test_data.sql +++ b/iam-persistence/src/main/resources/db/migration/test/V100000___test_data.sql @@ -9,34 +9,30 @@ INSERT INTO system_scope(scope, description, icon, restricted, default_scope, st INSERT INTO client_details (id, client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection, - token_endpoint_auth_method, require_auth_time, device_code_validity_seconds, created_at, active) VALUES - (1, 'client', 'secret', 'Test Client', false, null, 3600, 600, true, 'SECRET_BASIC',false, null, CURRENT_TIMESTAMP(), true), - (2, 'tasks-app', 'secret', 'Tasks App', false, null, 0, 0, true, 'SECRET_BASIC',false, null, CURRENT_TIMESTAMP(), true), - (3, 'post-client', 'secret', 'Post client', false, null, 3600,600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), true), - (4, 'client-cred', 'secret', 'Client credentials', false, null, 3600, 600, true, 'SECRET_BASIC',false, null, CURRENT_TIMESTAMP(), true), - (5, 'password-grant', 'secret', 'Password grant client', false, null, 3600, 600, true, 'SECRET_BASIC',true, null, CURRENT_TIMESTAMP(), true), - (6, 'scim-client-ro', 'secret', 'SCIM client (read-only)', false, null, 3600, 600, true, 'SECRET_POST',false, 600, CURRENT_TIMESTAMP(), true), - (7, 'scim-client-rw', 'secret', 'SCIM client (read-write)', false, null, 3600, 600, true, 'SECRET_POST',false, 600, CURRENT_TIMESTAMP(), true), - (8, 'token-exchange-actor', 'secret', 'Token Exchange grant client actor', false, null, 3600, 600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), true), - (9, 'token-exchange-subject', 'secret', 'Token Exchange grant client subject', false, null, 3600, 600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), true), - (10, 'registration-client', 'secret', 'Registration service test client', false, null, 3600, 600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), true), - (11, 'token-lookup-client', 'secret', 'Token lookup client', false, null, 3600, 600, true, 'SECRET_BASIC', false, null, CURRENT_TIMESTAMP(), true), - (12, 'device-code-client', 'secret', 'Device code client', false, null, 3600, 600, true, 'SECRET_BASIC', false, 600, CURRENT_TIMESTAMP(), true), - (13, 'implicit-flow-client', null, 'Implicit Flow client', false, null, 3600, 600, false, null, false, 600, CURRENT_TIMESTAMP(), true), - (14, 'public-dc-client', null, 'Public Device Code client', false, null, 3600, 600, false, null, false, 600, CURRENT_TIMESTAMP(), true), - (17, 'admin-client-ro', 'secret', 'Admin client (read-only)', false, null, 3600, 600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), true), - (18, 'admin-client-rw', 'secret', 'Admin client (read-write)', false, null, 3600, 600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), true), - (19, 'public-client', null, 'Public client', false, 3600, 3600, 600, true, 'NONE', false, null, CURRENT_TIMESTAMP(), true), - (20, 'refresh-client', 'secret', 'Refresh Flow client', false, 36000, 3600, 600, true, 'SECRET_BASIC', true, 30, CURRENT_TIMESTAMP(), true); + token_endpoint_auth_method, require_auth_time, device_code_validity_seconds, created_at, token_endpoint_auth_signing_alg, jwks, active) VALUES + (1, 'client', 'secret', 'Test Client', false, null, 3600, 600, true, 'SECRET_BASIC',false, null, CURRENT_TIMESTAMP(), null, null, true), + (2, 'tasks-app', 'secret', 'Tasks App', false, null, 0, 0, true, 'SECRET_BASIC',false, null, CURRENT_TIMESTAMP(), null, null, true), + (3, 'post-client', 'secret', 'Post client', false, null, 3600,600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), null, null, true), + (4, 'client-cred', 'secret', 'Client credentials', false, null, 3600, 600, true, 'SECRET_BASIC',false, null, CURRENT_TIMESTAMP(), null, null, true), + (5, 'password-grant', 'secret', 'Password grant client', false, null, 3600, 600, true, 'SECRET_BASIC',true, null, CURRENT_TIMESTAMP(), null, null, true), + (6, 'scim-client-ro', 'secret', 'SCIM client (read-only)', false, null, 3600, 600, true, 'SECRET_POST',false, 600, CURRENT_TIMESTAMP(), null, null, true), + (7, 'scim-client-rw', 'secret', 'SCIM client (read-write)', false, null, 3600, 600, true, 'SECRET_POST',false, 600, CURRENT_TIMESTAMP(), null, null, true), + (8, 'token-exchange-actor', 'secret', 'Token Exchange grant client actor', false, null, 3600, 600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), null, null, true), + (9, 'token-exchange-subject', 'secret', 'Token Exchange grant client subject', false, null, 3600, 600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), null, null, true), + (10, 'registration-client', 'secret', 'Registration service test client', false, null, 3600, 600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), null, null, true), + (11, 'token-lookup-client', 'secret', 'Token lookup client', false, null, 3600, 600, true, 'SECRET_BASIC', false, null, CURRENT_TIMESTAMP(), null, null, true), + (12, 'device-code-client', 'secret', 'Device code client', false, null, 3600, 600, true, 'SECRET_BASIC', false, 600, CURRENT_TIMESTAMP(), null, null, true), + (13, 'implicit-flow-client', null, 'Implicit Flow client', false, null, 3600, 600, false, null, false, 600, CURRENT_TIMESTAMP(), null, null, true), + (14, 'public-dc-client', null, 'Public Device Code client', false, null, 3600, 600, false, null, false, 600, CURRENT_TIMESTAMP(), null, null, true), + (15, 'jwt-auth-client_secret_jwt', 'secret', 'JWT Bearer Auth Client (client_secret_jwt)', false, null, 3600, 600, true, 'SECRET_JWT', false, null, CURRENT_TIMESTAMP(), 'HS256', null, true), + (16, 'jwt-auth-private_key_jwt', 'secret', 'JWT Bearer Auth Client (private_key_jwt)', false, null, 3600, 600, true,'PRIVATE_KEY', false, null, CURRENT_TIMESTAMP(), 'RS256', + '{"keys":[{"kty":"RSA","e":"AQAB","kid":"rsa1","n":"1y1CP181zqPNPlV1JDM7Xv0QnGswhSTHe8_XPZHxDTJkykpk_1BmgA3ovP62QRE2ORgsv5oSBI_Z_RaOc4Zx2FonjEJF2oBHtBjsAiF-pxGkM5ZPjFNgFTGp1yUUBjFDcEeIGCwPEyYSt93sQIP_0DRbViMUnpyn3xgM_a1dO5brEWR2n1Uqff1yA5NXfLS03qpl2dpH4HFY5-Zs4bvtJykpAOhoHuIQbz-hmxb9MZ3uTAwsx2HiyEJtz-suyTBHO3BM2o8UcCeyfa34ShPB8i86-sf78fOk2KeRIW1Bju3ANmdV3sxL0j29cesxKCZ06u2ZiGR3Srbft8EdLPzf-w"}]}', true), + (17, 'admin-client-ro', 'secret', 'Admin client (read-only)', false, null, 3600, 600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), null, null, true), + (18, 'admin-client-rw', 'secret', 'Admin client (read-write)', false, null, 3600, 600, true, 'SECRET_POST',false, null, CURRENT_TIMESTAMP(), null, null, true), + (19, 'public-client', null, 'Public client', false, 3600, 3600, 600, true, 'NONE', false, null, CURRENT_TIMESTAMP(), null, null, true), + (20, 'refresh-client', 'secret', 'Refresh Flow client', false, 36000, 3600, 600, true, 'SECRET_BASIC', true, 30, CURRENT_TIMESTAMP(), null, null, true); -INSERT INTO client_details (id, client_id, client_secret, client_name, dynamically_registered, - refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection, - token_endpoint_auth_method, require_auth_time, token_endpoint_auth_signing_alg, jwks) VALUES - (15, 'jwt-auth-client_secret_jwt', 'c8e9eed0-e6e4-4a66-b16e-6f37096356a7', 'JWT Bearer Auth Client (client_secret_jwt)', - false, null, 3600, 600, true, 'SECRET_JWT', false, 'HS256', null), - (16, 'jwt-auth-private_key_jwt', 'secret', 'JWT Bearer Auth Client (private_key_jwt)', - false, null, 3600, 600, true,'PRIVATE_KEY', false, 'RS256', - '{"keys":[{"kty":"RSA","e":"AQAB","kid":"rsa1","n":"1y1CP181zqPNPlV1JDM7Xv0QnGswhSTHe8_XPZHxDTJkykpk_1BmgA3ovP62QRE2ORgsv5oSBI_Z_RaOc4Zx2FonjEJF2oBHtBjsAiF-pxGkM5ZPjFNgFTGp1yUUBjFDcEeIGCwPEyYSt93sQIP_0DRbViMUnpyn3xgM_a1dO5brEWR2n1Uqff1yA5NXfLS03qpl2dpH4HFY5-Zs4bvtJykpAOhoHuIQbz-hmxb9MZ3uTAwsx2HiyEJtz-suyTBHO3BM2o8UcCeyfa34ShPB8i86-sf78fOk2KeRIW1Bju3ANmdV3sxL0j29cesxKCZ06u2ZiGR3Srbft8EdLPzf-w"}]}'); +UPDATE client_details SET client_secret = '$2a$12$P6AOS2.9DS6L.VaI1qjWnuVFbqUlpU449WDpISytV23H2ANUq0Gtu' WHERE client_secret = 'secret'; INSERT INTO client_scope (owner_id, scope) VALUES (1, 'openid'),