Skip to content

Commit 71abb8c

Browse files
Merge branch 'main' into 'deploy/base'
Main See merge request igrp-3_0/igrp-platform-access-management!240
2 parents efee242 + 1704b9c commit 71abb8c

33 files changed

+1585
-110
lines changed

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ IGRP_KEYCLOAK_CLIENT_ID=access-management
1818
IGRP_KEYCLOAK_CLIENT_SECRET=************
1919
IGRP_KEYCLOAK_GRANT_TYPE=client_credentials
2020

21+
# M2M Sync Token (used to authenticate requests from other modules/microservices to iGRP Access Management)
22+
IGRP_ACCESS_M2M_SYNC_TOKEN=my-m2m-sync-token
23+
2124
# File Storage configuration (Choose between 'minio' and 's3')
2225
IGRP_STORAGE_PROVIDER=s3
2326
IGRP_STORAGE_SECURITY=false
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
{
2+
"type": "controller",
3+
"name": "M2M",
4+
"module": "m2m",
5+
"description": "Machine-to-Machine",
6+
"basePath": "api",
7+
"actions": [
8+
{
9+
"actionName": "syncPermissions",
10+
"path": "m2m/sync/permissions",
11+
"method": "POST",
12+
"requestBody": {
13+
"content": {
14+
"application/json": {
15+
"schema": {
16+
"type": "PermissionDTO",
17+
"collectionType": "list",
18+
"name": "data",
19+
"objectType": "dto",
20+
"module": "shared"
21+
}
22+
}
23+
}
24+
},
25+
"responses": {
26+
"204": {
27+
"name": "OK",
28+
"content": {
29+
"application/json": {
30+
"schema": {
31+
"type": "string",
32+
"name": "data",
33+
"collectionType": "none"
34+
}
35+
}
36+
}
37+
}
38+
}
39+
},
40+
{
41+
"actionName": "syncResources",
42+
"path": "m2m/sync/resources",
43+
"method": "POST",
44+
"requestBody": {
45+
"content": {
46+
"application/json": {
47+
"schema": {
48+
"type": "ResourceDTO",
49+
"collectionType": "none",
50+
"name": "data",
51+
"objectType": "dto",
52+
"module": "shared"
53+
}
54+
}
55+
}
56+
},
57+
"responses": {
58+
"204": {
59+
"name": "OK",
60+
"content": {
61+
"application/json": {
62+
"schema": {
63+
"type": "string",
64+
"name": "data",
65+
"collectionType": "none"
66+
}
67+
}
68+
}
69+
}
70+
}
71+
},
72+
{
73+
"actionName": "syncApplications",
74+
"path": "m2m/sync/applications",
75+
"method": "POST",
76+
"requestBody": {
77+
"content": {
78+
"application/json": {
79+
"schema": {
80+
"type": "ApplicationDTO",
81+
"collectionType": "none",
82+
"name": "data",
83+
"objectType": "dto",
84+
"module": "shared"
85+
}
86+
}
87+
}
88+
},
89+
"responses": {
90+
"204": {
91+
"name": "OK",
92+
"content": {
93+
"application/json": {
94+
"schema": {
95+
"type": "string",
96+
"name": "data",
97+
"collectionType": "none"
98+
}
99+
}
100+
}
101+
}
102+
}
103+
}
104+
],
105+
"id": "jws7h4f2af"
106+
}

.igrpstudio/m2m/dto/.gitkeep

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This file is used to let Git track empty directories.

.igrpstudio/m2m/models/.gitkeep

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This file is used to let Git track empty directories.

.igrpstudio/m2m/module.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "module",
3+
"name": "m2m"
4+
}

.igrpstudio/shared/dto/PermissionDTO.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"isEmail": false,
3030
"isUrl": false,
3131
"primaryKey": false,
32-
"regex": "^[A-Za-z0-9_-]+$"
32+
"regex": "^[A-Za-z0-9._-]+$"
3333
},
3434
{
3535
"name": "description",
@@ -61,7 +61,7 @@
6161
"name": "departmentCode",
6262
"objectType": "java",
6363
"type": "string",
64-
"required": true,
64+
"required": false,
6565
"before": false,
6666
"after": false,
6767
"positive": false,

docs/PAGE_DETECTION_SPECS.md

Lines changed: 88 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,8 @@ At runtime, the **Spring Boot IAM SDK** (specific to each provider) synchronizes
540540
│ ↓ │
541541
│ Source Generator → Generates PermissionsRegistry.java │
542542
│ ↓ (Runtime) │
543-
│ IAM SDK (Provider-Specific)
544-
PermissionSyncRunner
543+
IAM Core SDK
544+
AuthorizationSyncRunner
545545
│ ↓ │
546546
│ AccessManagementClient → Access Management API │
547547
│ │
@@ -749,7 +749,7 @@ public final class PermissionsRegistry {
749749

750750
**5. Runtime Synchronization via Provider SDK**
751751

752-
The IAM Core has a **Spring Boot SDK module** that includes an autoconfigured `PermissionSyncRunner`.
752+
The IAM Core has a **Spring Boot SDK module** that includes an autoconfigured `AuthorizationSyncRunner`.
753753

754754
This runner:
755755

@@ -764,19 +764,21 @@ This runner:
764764
**Package**:
765765
`cv.igrp.framework.auth.core.autoconfig`
766766

767-
**PermissionSyncRunner.java**
767+
**AuthorizationSyncRunner.java**
768768

769769
```java
770770
package cv.igrp.framework.auth.core.autoconfig;
771771

772772
import cv.igrp.framework.auth.generated.PermissionsRegistry;
773773
import cv.igrp.platform.access.client.ApiClient;
774+
import cv.igrp.platform.access.client.api.M2MApi;
774775
import cv.igrp.platform.access.client.constants.Status;
775776
import cv.igrp.platform.access.client.model.PermissionDTO;
777+
import cv.igrp.platform.access.client.model.ResourceDTO;
776778
import jakarta.annotation.PostConstruct;
777779
import org.slf4j.Logger;
778780
import org.slf4j.LoggerFactory;
779-
import org.springframework.context.annotation.Conditional;
781+
import org.springframework.beans.factory.annotation.Value;
780782
import org.springframework.stereotype.Component;
781783

782784
import java.util.Arrays;
@@ -786,33 +788,59 @@ import java.util.List;
786788
* Automatically synchronizes code-defined permissions with the Access Management API.
787789
*/
788790
@Component
789-
public class PermissionSyncRunner {
791+
public class AuthorizationSyncRunner {
790792

791-
private static final Logger LOGGER = LoggerFactory.getLogger(PermissionSyncRunner.class);
793+
private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationSyncRunner.class);
792794

793795
private final ApiClient accessClient;
794796

795-
public PermissionSyncRunner(ApiClient accessClient) {
797+
@Value("${igrp.access.m2m.sync-token:}")
798+
private String m2mToken;
799+
800+
@Value("${spring.application.name:}")
801+
private String applicationName;
802+
803+
public AuthorizationSyncRunner(ApiClient accessClient) {
796804
this.accessClient = accessClient;
797805
}
798806

799807
@PostConstruct
800-
public void syncPermissions() {
808+
public void syncAuthorization() {
801809
try {
802-
LOGGER.info("[Permission Sync] Starting permission synchronization with Access Management API...");
810+
LOGGER.info("[Authorization Sync] Starting authorization synchronization with Access Management API...");
803811

804812
List<PermissionDTO> permissions = Arrays.stream(PermissionsRegistry.Permission.values())
805-
.map(p -> new PermissionDTO()
806-
.setName(p.getCode())
807-
.setDescription(p.getDescription())
808-
.setStatus(p.enabled() ? Status.ACTIVE : Status.INACTIVE))
813+
.map(p -> {
814+
var perm = new PermissionDTO();
815+
perm.setName(p.getCode());
816+
perm.setDescription(p.getDescription());
817+
perm.setStatus(p.enabled() ? Status.ACTIVE : Status.INACTIVE);
818+
return perm;
819+
})
809820
.toList();
810821

811-
accessClient.syncPermissions(permissions);
822+
M2MApi m2mApi = new M2MApi(accessClient);
823+
824+
ResourceDTO resource = new ResourceDTO();
825+
826+
resource.setName(applicationName);
827+
resource.setType("API");
828+
resource.setDescription("Resource for application: " + applicationName);
829+
830+
LOGGER.info("[Authorization Sync] Synchronizing resource for application '{}'", applicationName);
831+
832+
m2mApi.syncResources(resource, m2mToken, applicationName);
833+
834+
LOGGER.info("[Authorization Sync] Resource synchronization completed.");
835+
836+
LOGGER.info("[Authorization Sync] Synchronizing {} permissions for application '{}'", permissions.size(), applicationName);
837+
838+
m2mApi.syncPermissions(permissions, m2mToken, applicationName);
812839

813840
LOGGER.info("[Permission Sync] Successfully synchronized {} permissions.", permissions.size());
841+
814842
} catch (Exception ex) {
815-
LOGGER.error("[Permission Sync] Failed to synchronize permissions with Access Management API", ex);
843+
LOGGER.error("[Permission Sync] Failed to synchronize authorization with Access Management API", ex);
816844
}
817845
}
818846
}
@@ -834,10 +862,9 @@ cv.igrp.framework.auth.core.autoconfig.AutoConfiguration
834862
```java
835863
package cv.igrp.framework.auth.core.autoconfig;
836864

837-
import cv.igrp.framework.auth.core.client.AccessManagementClient;
865+
import cv.igrp.platform.access.client.ApiClient;
866+
import org.springframework.beans.factory.annotation.Value;
838867
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
839-
import org.springframework.boot.context.properties.ConfigurationProperties;
840-
import org.springframework.boot.context.properties.EnableConfigurationProperties;
841868
import org.springframework.context.annotation.Bean;
842869
import org.springframework.context.annotation.Configuration;
843870

@@ -856,23 +883,27 @@ public class AutoConfiguration {
856883

857884
---
858885

859-
**9. Configuration Example**
886+
**8. Configuration Example**
860887

861888
In the business microservice:
862889

890+
- Must indicate the URL of the Access Management API
891+
- Provide the machine-to-machine sync token
892+
863893
```properties
864894
igrp.access.api.base-url=http://access-management-service:8080
895+
igrp.access.m2m.sync-token=igrp-access-m2m-sync-token-1234
865896
```
866897

867898
The SDK will automatically:
868899

869900
* Generate `PermissionsRegistry` at build time
870-
* Run `PermissionSyncRunner` on startup
871-
* Sync all permissions to the Access Management API
901+
* Run `AuthorizationSyncRunner` on startup
902+
* Sync the resource and all permissions to the Access Management API
872903

873904
---
874905

875-
**10. @PreAuthorize Integration**
906+
**9. @PreAuthorize Integration**
876907

877908
For a Strapi based approach, we must define the following bean in the Spring context:
878909

@@ -894,13 +925,38 @@ import java.util.stream.Collectors;
894925
public class PermissionsBeanConfig {
895926

896927
@Bean(name = "permissions")
897-
public Map<String, String> permissions() {
898-
return Map.ofEntries(
899-
PermissionsRegistry.Permission.values()
900-
.stream()
928+
public PermissionAccessor permissions() {
929+
Map<String, String> map = Map.ofEntries(
930+
Arrays.stream(PermissionsRegistry.Permission.values())
901931
.map(p -> Map.entry(p.name(), p.getCode()))
902932
.toArray(Map.Entry[]::new)
903933
);
934+
return new PermissionAccessor(map);
935+
}
936+
}
937+
```
938+
939+
To use an accessor for the permissions entries we define the following class:
940+
941+
```java
942+
package cv.igrp.framework.auth.core.config;
943+
944+
import java.util.Map;
945+
946+
public class PermissionAccessor {
947+
948+
private final Map<String, String> permissions;
949+
950+
public PermissionAccessor(Map<String, String> permissions) {
951+
this.permissions = permissions;
952+
}
953+
954+
public String get(String key) {
955+
return permissions.get(key);
956+
}
957+
958+
public Object getProperty(String name) {
959+
return permissions.get(name);
904960
}
905961
}
906962
```
@@ -927,14 +983,14 @@ public class IgrpAuthorizationService {
927983
this.authHelper = authHelper;
928984
}
929985

930-
public boolean checkPermission(String resource, String action) {
986+
public boolean checkPermission(String action) {
931987
try {
932988
String token = authHelper.getToken();
933989
client.setAuthToken(token);
934990
AuthorizeApi authorizeApi = new AuthorizeApi(client);
935991

936992
return authorizeApi.checkAuthorization(
937-
new PermissionCheckRequestDTO(resource,
993+
new PermissionCheckRequestDTO(null,
938994
action)
939995
).isAllowed();
940996
} catch (Exception e) {
@@ -947,14 +1003,14 @@ public class IgrpAuthorizationService {
9471003
Permissions can be referenced directly in code with constants generated at build time:
9481004

9491005
```java
950-
@PreAuthorize("@igrpAuthorization.checkPermission(permissions.USER_EDIT)")
1006+
@PreAuthorize("@igrpAuthorization.checkPermission(@permissions.get('USER_EDIT'))")
9511007
public ResponseEntity<?> updateUser(...) {
9521008
// business logic
9531009
}
9541010
```
9551011
---
9561012

957-
**11. Advantages**
1013+
**10. Advantages**
9581014

9591015
| Aspect | Description |
9601016
| ------------------------------- |--------------------------------------------------------------------------------|
@@ -967,7 +1023,7 @@ public ResponseEntity<?> updateUser(...) {
9671023

9681024
---
9691025

970-
**12. Extensions**
1026+
**11. Extensions**
9711027

9721028
* **iGRP Studio Integration:**
9731029
The iGRP Studio could parse these annotated classes, display them in a UI, allow editing, and regenerate the corresponding annotated permission files.

0 commit comments

Comments
 (0)