Skip to content

Commit

Permalink
Merge pull request #371 from Consdata/IKC-394-user-groups-permissions
Browse files Browse the repository at this point in the history
IKC-394 User groups permissions
  • Loading branch information
pbelke authored Sep 8, 2024
2 parents dd50fdc + ad5bed8 commit 119932c
Show file tree
Hide file tree
Showing 48 changed files with 808 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.consdata.kouncil.config;

import com.consdata.kouncil.track.DestinationStore;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
Expand All @@ -15,9 +17,6 @@
import org.springframework.web.socket.messaging.SessionSubscribeEvent;
import org.springframework.web.socket.messaging.SessionUnsubscribeEvent;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Configuration
@EnableWebSocketMessageBroker
Expand All @@ -34,7 +33,7 @@ public WebSocketConfig(DestinationStore destinationStore) {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.enableSimpleBroker("/topic", "/notifications");
config.setApplicationDestinationPrefixes("/app");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.consdata.kouncil.config.security;

import com.consdata.kouncil.notifications.Notification;
import com.consdata.kouncil.notifications.NotificationAction;
import com.consdata.kouncil.notifications.NotificationType;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.simp.SimpMessagingTemplate;

@RequiredArgsConstructor
public class DefaultUserPermissionsReloader implements UserPermissionsReloader {

private final SimpMessagingTemplate eventSender;

@Override
public void reloadPermissions() {
Notification notification = new Notification();
notification.setMessage("User permissions were updated. You have to re-login.");
notification.setType(NotificationType.PUSH_WITH_ACTION_REQUIRED);
notification.setAction(NotificationAction.LOGOUT);
eventSender.convertAndSend("/notifications", notification);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.consdata.kouncil.config.security;

public interface UserPermissionsReloader {

void reloadPermissions();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.consdata.kouncil.config.security.ad;

import com.consdata.kouncil.config.security.DefaultUserPermissionsReloader;
import com.consdata.kouncil.security.UserRolesMapping;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand All @@ -8,6 +9,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
Expand All @@ -28,6 +30,7 @@
public class ActiveDirectoryWebSecurityConfig {

private final UserRolesMapping userRolesMapping;
private final SimpMessagingTemplate eventSender;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand Down Expand Up @@ -55,6 +58,11 @@ GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
}

@Bean
public DefaultUserPermissionsReloader userPermissionsReloader(){
return new DefaultUserPermissionsReloader(eventSender);
}

@Value("${kouncil.auth.ad.domain:}")
public String domain;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.consdata.kouncil.config.security.inmemory;

import static com.consdata.kouncil.config.security.inmemory.InMemoryConst.ADMIN_CONFIG;
import static com.consdata.kouncil.config.security.inmemory.InMemoryConst.ADMIN_USERNAME;
import static com.consdata.kouncil.config.security.inmemory.InMemoryConst.EDITOR_CONFIG;
import static com.consdata.kouncil.config.security.inmemory.InMemoryConst.EDITOR_USERNAME;
import static com.consdata.kouncil.config.security.inmemory.InMemoryConst.SUPERUSER_CONFIG;
import static com.consdata.kouncil.config.security.inmemory.InMemoryConst.SUPERUSER_USERNAME;
import static com.consdata.kouncil.config.security.inmemory.InMemoryConst.VIEWER_CONFIG;
import static com.consdata.kouncil.config.security.inmemory.InMemoryConst.VIEWER_USERNAME;

import com.consdata.kouncil.KouncilRuntimeException;
import com.consdata.kouncil.config.security.DefaultUserPermissionsReloader;
import com.consdata.kouncil.security.UserRolesMapping;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Service;

@Service
@ConditionalOnProperty(prefix = "kouncil.auth", name = "active-provider", havingValue = "inmemory")
public class InMemoryUserPermissionsReloader extends DefaultUserPermissionsReloader {

private final UserDetailsManager userDetailsService;
private final UserRolesMapping userRolesMapping;

public InMemoryUserPermissionsReloader(SimpMessagingTemplate eventSender, UserDetailsManager userDetailsService,
UserRolesMapping userRolesMapping) {
super(eventSender);
this.userDetailsService = userDetailsService;
this.userRolesMapping = userRolesMapping;
}

@Override
public void reloadPermissions() {
super.reloadPermissions();
List.of(ADMIN_USERNAME, EDITOR_USERNAME, VIEWER_USERNAME, SUPERUSER_USERNAME).forEach(user -> {
try {
String[] fileContent = Files.readString(getPath(user)).split(";");
userDetailsService.updateUser(User.withUsername(user)
.password(String.format("{noop}%s", fileContent[0]))
.authorities(userRolesMapping.mapToKouncilRoles(Set.of(fileContent[1].split(","))))
.build());
} catch (IOException e) {
throw new KouncilRuntimeException(e);
}
});
}

private Path getPath(String username) {
return switch (username) {
case ADMIN_USERNAME -> Paths.get(ADMIN_CONFIG);
case EDITOR_USERNAME -> Paths.get(EDITOR_CONFIG);
case VIEWER_USERNAME -> Paths.get(VIEWER_CONFIG);
case SUPERUSER_USERNAME -> Paths.get(SUPERUSER_CONFIG);
default -> throw new IllegalStateException(String.format("Can't find user: %s", SecurityContextHolder.getContext().getAuthentication().getName()));
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.consdata.kouncil.config.security.ldap;

import com.consdata.kouncil.config.security.DefaultUserPermissionsReloader;
import com.consdata.kouncil.security.UserRolesMapping;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand All @@ -9,6 +10,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
Expand All @@ -32,6 +34,7 @@
public class LdapWebSecurityConfig {

private final UserRolesMapping userRolesMapping;
private final SimpMessagingTemplate eventSender;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand Down Expand Up @@ -65,6 +68,11 @@ public AuthenticationManager authenticationManager(HttpSecurity http) throws Exc
.build();
}

@Bean
public DefaultUserPermissionsReloader userPermissionsReloader(){
return new DefaultUserPermissionsReloader(eventSender);
}

@Value("${kouncil.auth.ldap.provider-url:}")
private String providerUrl;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.consdata.kouncil.config.security.sso;

import com.consdata.kouncil.config.security.DefaultUserPermissionsReloader;
import com.consdata.kouncil.security.UserRolesMapping;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
Expand All @@ -14,6 +15,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
Expand All @@ -37,8 +39,8 @@
public class SSOWebSecurityConfig {

private final ObjectMapper mapper;

private final UserRolesMapping userRolesMapping;
private final SimpMessagingTemplate eventSender;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand Down Expand Up @@ -83,6 +85,11 @@ GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
}

@Bean
public DefaultUserPermissionsReloader userPermissionsReloader(){
return new DefaultUserPermissionsReloader(eventSender);
}

private GrantedAuthoritiesMapper authoritiesMapper() {
return authorities -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.consdata.kouncil.notifications;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class Notification {

private String message;
private NotificationType type;
private NotificationAction action;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.consdata.kouncil.notifications;

public enum NotificationAction {

LOGOUT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.consdata.kouncil.notifications;

public enum NotificationType {
PUSH_WITH_ACTION_REQUIRED,
PUSH
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.consdata.kouncil.security.group;

import com.consdata.kouncil.KouncilRuntimeException;
import com.consdata.kouncil.config.security.UserPermissionsReloader;
import com.consdata.kouncil.model.admin.UserGroup;
import com.consdata.kouncil.security.group.dto.UserGroupDto;
import lombok.RequiredArgsConstructor;
Expand All @@ -11,6 +12,7 @@
public class UserGroupService {

private final UserGroupRepository userGroupRepository;
private final UserPermissionsReloader userPermissionsReloader;

public UserGroupDto getUserGroup(Long id) {
return UserGroupConverter.convertToUserGroupDto(findById(id));
Expand All @@ -26,10 +28,12 @@ public void createUserGroup(UserGroupDto userGroup) {

public void updateUserGroup(UserGroupDto userGroup) {
userGroupRepository.save(UserGroupConverter.updateUserGroup(userGroup, findById(userGroup.getId())));
userPermissionsReloader.reloadPermissions();
}

public void deleteUserGroup(Long id) {
userGroupRepository.deleteById(id);
userPermissionsReloader.reloadPermissions();
}

public boolean isUserGroupCodeUnique(Long id, String userGroupName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import javax.annotation.security.RolesAllowed;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -21,4 +23,10 @@ public class UserGroupsController {
public List<UserGroupDto> getUserGroups() {
return userGroupsService.getUserGroups();
}

@RolesAllowed(Fields.USER_GROUPS)
@PostMapping
public void updatePermissions(@RequestBody List<UserGroupDto> userGroupDtoList) {
userGroupsService.saveAll(userGroupDtoList);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.consdata.kouncil.security.group;

import com.consdata.kouncil.config.security.UserPermissionsReloader;
import com.consdata.kouncil.security.group.dto.UserGroupDto;
import java.util.List;
import java.util.stream.StreamSupport;
Expand All @@ -11,10 +12,16 @@
public class UserGroupsService {

private final UserGroupRepository userGroupRepository;
private final UserPermissionsReloader userPermissionsReloader;

public List<UserGroupDto> getUserGroups() {
return StreamSupport.stream(userGroupRepository.findAll().spliterator(), false)
.map(UserGroupConverter::convertToUserGroupDto)
.toList();
}

public void saveAll(List<UserGroupDto> userGroupDtoList) {
userGroupRepository.saveAll(userGroupDtoList.stream().map(UserGroupConverter::convertToUserGroup).toList());
userPermissionsReloader.reloadPermissions();
}
}
1 change: 1 addition & 0 deletions kouncil-frontend/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"feat-confirm": "libs/feat-confirm",
"feat-favourites": "libs/feat-favourites",
"feat-no-data": "libs/feat-no-data",
"feat-notifications": "libs/feat-notifications",
"feat-send": "libs/feat-send",
"feat-topic-form": "libs/feat-topic-form",
"feat-topics": "libs/feat-topics",
Expand Down
5 changes: 3 additions & 2 deletions kouncil-frontend/apps/kouncil/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ import {
UserGroupService,
UserGroupsService
} from '@app/feat-user-groups';
import {RxStompService} from './rx-stomp.service';
import {RX_STOMP_CONFIG} from './rx-stomp.config';
import {FeatNotificationsModule, RxStompService} from '@app/feat-notifications';

export const BASE_URL = new InjectionToken('BASE_URL');

Expand Down Expand Up @@ -231,7 +231,8 @@ export function authServiceFactory(http: HttpClient, baseUrl: string): AuthServi
CommonAuthModule,
FeatTopicFormModule,
FeatClustersModule,
FeatUserGroupsModule
FeatUserGroupsModule,
FeatNotificationsModule
],
providers: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Component, Inject, LOCALE_ID, OnDestroy, OnInit} from '@angular/core';
import {interval, Observable, Subscription} from 'rxjs';
import {interval, Subscription} from 'rxjs';
import {ActivatedRoute} from '@angular/router';
import {ConsumerGroupService} from './consumer-group.service';
import {switchMap, tap} from 'rxjs/operators';
Expand Down Expand Up @@ -61,7 +61,6 @@ export class ConsumerGroupComponent extends AbstractTableComponent implements On
filteredAssignments: ConsumerGroupOffset[] = [];
paused: boolean = false;
lastLags: IHash = {};
loading$: Observable<boolean> = this.progressBarService.loading$;

additionalColumns: TableColumn[] = [
{
Expand Down
Loading

0 comments on commit 119932c

Please sign in to comment.