Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 140 additions & 1 deletion src/main/java/com/ecmsp/userservice/api/grpc/UserGrpcService.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.ecmsp.userservice.api.grpc;

import com.ecmsp.user.v1.*;
import com.ecmsp.userservice.api.grpc.context.ContextAuthorization;
import com.ecmsp.userservice.api.grpc.context.UserContextData;
import com.ecmsp.userservice.api.grpc.context.UserContextGrpcHolder;
import com.ecmsp.userservice.user.domain.Permission;
import com.ecmsp.userservice.user.domain.RoleFacade;
import com.ecmsp.userservice.user.domain.RoleToCreate;
import com.ecmsp.userservice.user.domain.UserFacade;
import com.ecmsp.userservice.user.domain.UserView;
import io.grpc.Context;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
Expand All @@ -26,12 +30,18 @@ public class UserGrpcService extends UserServiceGrpc.UserServiceImplBase {
private final RoleFacade roleFacade;
private final UserGrpcMapper mapper;
private final RoleGrpcMapper roleMapper;
private final ContextAuthorization contextAuthorization;

public UserGrpcService(UserFacade userFacade, RoleFacade roleFacade, UserGrpcMapper mapper, RoleGrpcMapper roleMapper) {
public UserGrpcService(UserFacade userFacade,
RoleFacade roleFacade,
UserGrpcMapper mapper,
RoleGrpcMapper roleMapper,
ContextAuthorization contextAuthorization) {
this.userFacade = userFacade;
this.roleFacade = roleFacade;
this.mapper = mapper;
this.roleMapper = roleMapper;
this.contextAuthorization = contextAuthorization;
}

@Override
Expand All @@ -40,6 +50,19 @@ public void getUser(GetUserRequest request, StreamObserver<GetUserResponse> resp
com.ecmsp.user.v1.UserId protoUserId = com.ecmsp.user.v1.UserId.newBuilder()
.setValue(request.getUserId())
.build();

UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!request.getUserId().equals(userContextData.userId()) || !contextAuthorization.isHimselfOrHasPermission(userContextData,
request.getUserId(),
Permission.READ_USERS)) {

responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to access user with id: " + request.getUserId()
+ "need to be himself or have READ_USERS permission")
.asRuntimeException());
return;
}

com.ecmsp.userservice.user.domain.UserId userId = mapper.toDomainUserId(protoUserId);
Optional<com.ecmsp.userservice.user.domain.User> userOptional = userFacade.findUserById(userId);

Expand Down Expand Up @@ -123,6 +146,19 @@ public void createUser(CreateUserRequest request, StreamObserver<CreateUserRespo
public void updateUser(UpdateUserRequest request, StreamObserver<UpdateUserResponse> responseObserver) {
try {
com.ecmsp.userservice.user.domain.UserId userId = mapper.toDomainUserId(request.getUser().getId());

UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!userId.value().toString().equals(userContextData.userId()) || !contextAuthorization.isHimselfOrHasPermission(userContextData,
userId.value().toString(),
Permission.MANAGE_USERS)) {

responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to access user with id: " + userId.value()
+ " need to be himself or have MANAGE_USERS permission")
.asRuntimeException());
return;
}

String newLogin = request.getUser().getLogin();

if (newLogin == null || newLogin.isBlank()) {
Expand Down Expand Up @@ -170,6 +206,18 @@ public void deleteUser(DeleteUserRequest request, StreamObserver<DeleteUserRespo
.setValue(request.getUserId())
.build();
com.ecmsp.userservice.user.domain.UserId userId = mapper.toDomainUserId(protoUserId);

UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!userId.value().toString().equals(userContextData.userId()) || !contextAuthorization.isHimselfOrHasPermission(userContextData,
userId.value().toString(),
Permission.MANAGE_USERS)) {

responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to access user with id: " + userId.value()
+ " need to be himself or have MANAGE_USERS permission")
.asRuntimeException());
return;
}
userFacade.deleteUser(userId);

DeleteUserResponse response = DeleteUserResponse.newBuilder().build();
Expand All @@ -189,6 +237,17 @@ public void deleteUser(DeleteUserRequest request, StreamObserver<DeleteUserRespo
@Override
public void listUsers(ListUsersRequest request, StreamObserver<ListUsersResponse> responseObserver) {
try {
UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!contextAuthorization.hasPermission(
userContextData,
Permission.READ_USERS)) {

responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to list users, READ_USERS permission required")
.asRuntimeException());
return;
}

String filterLogin = request.getFilterLogin();
List<UserView> users = userFacade.listUsers(filterLogin);

Expand All @@ -214,6 +273,16 @@ public void listUsers(ListUsersRequest request, StreamObserver<ListUsersResponse
@Override
public void createRole(CreateRoleRequest request, StreamObserver<CreateRoleResponse> responseObserver) {
try {
UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!contextAuthorization.hasPermission(
userContextData,
Permission.MANAGE_ROLES)) {

responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to create role, MANAGE_ROLES permission required")
.asRuntimeException());
return;
}
log.info("Creating role: {}", request.getRole().getName());

RoleToCreate roleToCreate = roleMapper.toDomainRoleToCreate(request.getRole());
Expand Down Expand Up @@ -253,6 +322,17 @@ public void createRole(CreateRoleRequest request, StreamObserver<CreateRoleRespo
@Override
public void updateRole(UpdateRoleRequest request, StreamObserver<UpdateRoleResponse> responseObserver) {
try {
UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!contextAuthorization.hasPermission(
userContextData,
Permission.MANAGE_ROLES)) {

responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to update role, MANAGE_ROLES permission required")
.asRuntimeException());
return;
}

String roleName = request.getRole().getName();
log.info("Updating role: {}", roleName);

Expand Down Expand Up @@ -308,6 +388,15 @@ public void updateRole(UpdateRoleRequest request, StreamObserver<UpdateRoleRespo
@Override
public void deleteRole(DeleteRoleRequest request, StreamObserver<DeleteRoleResponse> responseObserver) {
try {
UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!contextAuthorization.hasPermission(
userContextData,
Permission.MANAGE_ROLES)) {
responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to delete role, MANAGE_ROLES permission required")
.asRuntimeException());
return;
}
String roleName = request.getRoleId();
log.info("Deleting role: {}", roleName);

Expand All @@ -334,6 +423,17 @@ public void deleteRole(DeleteRoleRequest request, StreamObserver<DeleteRoleRespo
@Override
public void listRoles(ListRolesRequest request, StreamObserver<ListRolesResponse> responseObserver) {
try {
UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!contextAuthorization.hasPermission(
userContextData,
Permission.MANAGE_ROLES)) {

responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to list roles, MANAGE_ROLES permission required")
.asRuntimeException());
return;
}

log.info("Listing all roles");

List<com.ecmsp.userservice.user.domain.Role> roles = roleFacade.getAllRoles();
Expand All @@ -360,6 +460,16 @@ public void listRoles(ListRolesRequest request, StreamObserver<ListRolesResponse
@Override
public void assignRoleToUsers(AssignRoleToUsersRequest request, StreamObserver<AssignRoleToUsersResponse> responseObserver) {
try {
UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!contextAuthorization.hasPermission(
userContextData,
Permission.MANAGE_ROLES)) {
responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to delete role, MANAGE_ROLES permission required")
.asRuntimeException());
return;
}

String roleName = request.getRoleName();
log.info("Assigning role {} to {} users", roleName, request.getUserIdsList().size());

Expand Down Expand Up @@ -396,6 +506,16 @@ public void assignRoleToUsers(AssignRoleToUsersRequest request, StreamObserver<A
@Override
public void removeRoleFromUsers(RemoveRoleFromUsersRequest request, StreamObserver<RemoveRoleFromUsersResponse> responseObserver) {
try {
UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!contextAuthorization.hasPermission(
userContextData,
Permission.MANAGE_ROLES)) {
responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to delete role, MANAGE_ROLES permission required")
.asRuntimeException());
return;
}

String roleName = request.getRoleName();
log.info("Removing role {} from {} users", roleName, request.getUserIdsList().size());

Expand Down Expand Up @@ -432,6 +552,25 @@ public void removeRoleFromUsers(RemoveRoleFromUsersRequest request, StreamObserv
@Override
public void listAllPermissions(ListAllPermissionsRequest request, StreamObserver<ListAllPermissionsResponse> responseObserver) {
try {
UserContextData userContextData = UserContextGrpcHolder.getUserContext();
if(!contextAuthorization.hasPermission(
userContextData,
Permission.MANAGE_ROLES)) {
responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to delete role, MANAGE_ROLES permission required")
.asRuntimeException());
return;
}

if(!contextAuthorization.hasPermission(
UserContextGrpcHolder.getUserContext(),
Permission.MANAGE_ROLES)) {

responseObserver.onError(Status.PERMISSION_DENIED
.withDescription("Permission denied to list all permissions, MANAGE_ROLES permission required")
.asRuntimeException());
return;
}
log.info("Listing all available permissions");

List<String> permissions = Arrays.stream(Permission.values())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ecmsp.userservice.api.grpc.context;

import com.ecmsp.userservice.user.domain.Permission;
import org.springframework.stereotype.Component;

@Component
public class ContextAuthorization {
public boolean hasPermission(UserContextData userContext, Permission permission) {
return userContext.permissions().contains(permission);
}

public boolean isHimselfOrHasPermission(UserContextData userContext, String userId, Permission permission) {
return userContext.userId().equals(userId) || hasPermission(userContext, permission);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package com.ecmsp.userservice.api.grpc.context;


import com.ecmsp.userservice.user.domain.Permission;

import java.util.Set;

public record UserContextData(String userId,
String login) {
String login,
Set<Permission> permissions) {
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.ecmsp.userservice.api.grpc.context;

import com.ecmsp.userservice.user.domain.Permission;
import io.grpc.*;
import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor;

import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;

@GrpcGlobalServerInterceptor
public class UserContextGrpcInterceptor implements ServerInterceptor {

Expand All @@ -11,10 +16,15 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {

UserContextData userContextData = new UserContextData(
headers.get(Metadata.Key.of("X-User-ID", Metadata.ASCII_STRING_MARSHALLER)),
headers.get(Metadata.Key.of("X-Login", Metadata.ASCII_STRING_MARSHALLER))
headers.get(Metadata.Key.of("X-Login", Metadata.ASCII_STRING_MARSHALLER)),
Arrays.stream(headers.get(Metadata.Key.of("X-Permissions", Metadata.ASCII_STRING_MARSHALLER))
.split(","))
.filter(s -> !s.trim().isBlank())
.map(Permission::getPermissionByName)
.filter(Optional::isPresent).map(Optional::get)
.collect(Collectors.toSet())
);

Context context = Context.current()
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/com/ecmsp/userservice/user/domain/Permission.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.ecmsp.userservice.user.domain;

import lombok.extern.slf4j.Slf4j;

import java.util.Optional;

@Slf4j
public enum Permission {
// Product permissions
WRITE_PRODUCTS,
Expand All @@ -11,12 +16,23 @@ public enum Permission {
CANCEL_ORDERS,

// User management permissions
READ_USERS,
MANAGE_USERS,

// Role management permissions
MANAGE_ROLES,

// Payment permissions
PROCESS_PAYMENTS,
REFUND_PAYMENTS
REFUND_PAYMENTS;

public static Optional<Permission> getPermissionByName(String name) {
for (Permission permission : Permission.values()) {
if (permission.name().equalsIgnoreCase(name)) {
return Optional.of(permission);
}
}
log.error("Permission with name {} not found", name);
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ INSERT INTO permissions (permission_name) VALUES
('PROCESS_PAYMENTS'),
('REFUND_PAYMENTS');

INSERT INTO roles (role_id, role_name) VALUES
(gen_random_uuid(), 'ADMIN'),
(gen_random_uuid(), 'MANAGER'),
(gen_random_uuid(), 'CUSTOMER_SUPPORT');

INSERT INTO role_permissions (role_id, permission_name) VALUES
((SELECT role_id FROM roles WHERE role_name = 'ADMIN'), 'WRITE_PRODUCTS'),
((SELECT role_id FROM roles WHERE role_name = 'ADMIN'), 'DELETE_PRODUCTS'),
((SELECT role_id FROM roles WHERE role_name = 'ADMIN'), 'READ_ORDERS'),
((SELECT role_id FROM roles WHERE role_name = 'ADMIN'), 'WRITE_ORDERS'),
((SELECT role_id FROM roles WHERE role_name = 'ADMIN'), 'CANCEL_ORDERS'),
((SELECT role_id FROM roles WHERE role_name = 'ADMIN'), 'MANAGE_USERS'),
((SELECT role_id FROM roles WHERE role_name = 'ADMIN'), 'MANAGE_ROLES'),
((SELECT role_id FROM roles WHERE role_name = 'ADMIN'), 'PROCESS_PAYMENTS'),
((SELECT role_id FROM roles WHERE role_name = 'ADMIN'), 'REFUND_PAYMENTS');

INSERT INTO user_roles (user_id, role_id) VALUES
((SELECT user_id FROM users WHERE login = 'andy'), (SELECT role_id FROM roles WHERE role_name = 'ADMIN'));

-- Create indexes for performance
CREATE INDEX idx_role_permissions_role_id ON role_permissions(role_id);
CREATE INDEX idx_user_roles_user_id ON user_roles(user_id);
Expand Down
Loading