Implemented authorisation
This commit is contained in:
parent
11ca677f65
commit
b48b375e4e
@ -0,0 +1,39 @@
|
||||
package ru.dragonestia.picker.api.exception;
|
||||
|
||||
import ru.dragonestia.picker.api.repository.response.ErrorResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class AccountDoesNotExistsException extends ApiException {
|
||||
|
||||
public static final String ERROR_ID = "err.account.does_not_exists";
|
||||
|
||||
private final String accountId;
|
||||
|
||||
public AccountDoesNotExistsException(String accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
public AccountDoesNotExistsException(ErrorResponse errorResponse) {
|
||||
this(errorResponse.details().get("accountId"));
|
||||
}
|
||||
|
||||
public String getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorId() {
|
||||
return ERROR_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Account with id '" + accountId + "' does not exists";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendDetailsToErrorResponse(Map<String, String> details) {
|
||||
details.put("accountId", accountId);
|
||||
}
|
||||
}
|
||||
@ -25,6 +25,7 @@ public class ExceptionFactory {
|
||||
factory.put(RoomAreFullException.ERROR_ID, RoomAreFullException::new);
|
||||
factory.put(RoomNotFoundException.ERROR_ID, RoomNotFoundException::new);
|
||||
factory.put(NotPersistedNodeException.ERROR_ID, NotPersistedNodeException::new);
|
||||
factory.put(AccountDoesNotExistsException.ERROR_ID, AccountDoesNotExistsException::new);
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
@ -2,8 +2,10 @@ package ru.dragonestia.picker.api.impl.repository;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import ru.dragonestia.picker.api.exception.AccountDoesNotExistsException;
|
||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||
import ru.dragonestia.picker.api.impl.util.RestTemplate;
|
||||
import ru.dragonestia.picker.api.impl.util.type.HttpMethod;
|
||||
import ru.dragonestia.picker.api.model.account.ResponseAccount;
|
||||
import ru.dragonestia.picker.api.repository.AccountRepository;
|
||||
|
||||
@ -20,6 +22,11 @@ public class AccountRepositoryImpl implements AccountRepository {
|
||||
|
||||
@Override
|
||||
public Optional<ResponseAccount> findAccountByUsername(@NotNull String username) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
try {
|
||||
var response = rest.query("/accounts/" + username, HttpMethod.GET, ResponseAccount.class);
|
||||
return Optional.of(response);
|
||||
} catch (AccountDoesNotExistsException ex) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,4 +8,4 @@ scrape_configs:
|
||||
scrape_interval: 5s
|
||||
metrics_path: '/actuator/prometheus'
|
||||
static_configs:
|
||||
- targets: [ 'host.docker.internal:8080' ]
|
||||
- targets: [ 'server:8080' ]
|
||||
|
||||
@ -7,6 +7,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import ru.dragonestia.picker.cp.page.LoginPage;
|
||||
import ru.dragonestia.picker.cp.service.AccountService;
|
||||
|
||||
@ -39,16 +40,17 @@ public class SecurityConfig extends VaadinWebSecurity {
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests(auth -> {
|
||||
|
||||
});
|
||||
|
||||
http.formLogin(login -> {
|
||||
login.successForwardUrl("/nodes");
|
||||
auth.requestMatchers(AntPathRequestMatcher.antMatcher("/static/**")).permitAll();
|
||||
});
|
||||
|
||||
http.userDetailsService(accountService);
|
||||
|
||||
super.configure(http);
|
||||
|
||||
http.formLogin(login -> {
|
||||
login.successForwardUrl("/nodes");
|
||||
login.defaultSuccessUrl("/nodes");
|
||||
});
|
||||
setLoginView(http, LoginPage.class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import com.vaadin.flow.server.ErrorEvent;
|
||||
import com.vaadin.flow.server.ErrorHandler;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import ru.dragonestia.picker.api.exception.ApiException;
|
||||
import ru.dragonestia.picker.api.impl.exception.NotEnoughPermissions;
|
||||
import ru.dragonestia.picker.cp.component.Notifications;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
@ -34,6 +35,13 @@ public class ApplicationErrorHandler implements ErrorHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorEvent.getThrowable() instanceof NotEnoughPermissions) {
|
||||
execute(() -> {
|
||||
Notifications.error("Not enough permissions to this action");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
execute(() -> {
|
||||
Notifications.error("Internal server error");
|
||||
});
|
||||
|
||||
@ -19,7 +19,7 @@ public class Account implements IAccount, UserDetails {
|
||||
public Account(ResponseAccount original, RoomPickerClient client) {
|
||||
this.original = original;
|
||||
this.client = client;
|
||||
permissions = original.getPermissions().stream().map(Permission::new).collect(Collectors.toSet());
|
||||
permissions = original.getPermissions().stream().map(permission -> new Permission("ROLE_" + permission)).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public @NotNull RoomPickerClient getClient() {
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
package ru.dragonestia.picker.cp.page;
|
||||
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.html.H1;
|
||||
import com.vaadin.flow.component.html.Paragraph;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.*;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
@Route(value = "/", layout = MainLayout.class)
|
||||
public class HomePage extends VerticalLayout {
|
||||
|
||||
public HomePage() {
|
||||
super();
|
||||
|
||||
var field = new TextField("Some field");
|
||||
field.setRequired(true);
|
||||
add(field);
|
||||
|
||||
var button = new Button("Click me", e -> {
|
||||
Notification.show(field.isInvalid() + "");
|
||||
});
|
||||
|
||||
add(button);
|
||||
}
|
||||
}
|
||||
@ -7,9 +7,11 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
@Log4j2
|
||||
@AnonymousAllowed
|
||||
@Route("/login")
|
||||
public class LoginPage extends VerticalLayout implements BeforeEnterObserver {
|
||||
|
||||
|
||||
@ -16,13 +16,11 @@ import ru.dragonestia.picker.cp.annotation.ServerURL;
|
||||
|
||||
public class MainLayout extends AppLayout {
|
||||
|
||||
private final RoomPickerClient client;
|
||||
private final RoomPickerInfoResponse serverInfo;
|
||||
private final String serverUrl;
|
||||
|
||||
public MainLayout(RoomPickerClient client, @ServerURL String serverUrl) {
|
||||
this.client = client;
|
||||
this.serverInfo = client.getServerInfo();
|
||||
public MainLayout(RoomPickerClient adminClient, @ServerURL String serverUrl) {
|
||||
this.serverInfo = adminClient.getServerInfo();
|
||||
this.serverUrl = serverUrl;
|
||||
|
||||
var toggle = new DrawerToggle();
|
||||
@ -47,7 +45,6 @@ public class MainLayout extends AppLayout {
|
||||
nav.addItem(new SideNavItem("Search users", UserSearchPage.class, VaadinIcon.SEARCH.create()));
|
||||
nav.addItem(new SideNavItem("Documentation", "https://github.com/ScarletRedMan/RoomPicker", VaadinIcon.BOOK.create()));
|
||||
nav.addItem(new SideNavItem("Swagger UI", serverUrl + "/api-docs-ui", VaadinIcon.CURLY_BRACKETS.create()));
|
||||
nav.addItem(new SideNavItem("Sign-out", HomePage.class, VaadinIcon.SIGN_OUT.create()));
|
||||
return nav;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||
@ -19,6 +20,7 @@ import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
@PermitAll
|
||||
@PageTitle("Rooms")
|
||||
@Route(value = "/nodes/:nodeId", layout = MainLayout.class)
|
||||
public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
||||
|
||||
@ -4,8 +4,8 @@ import com.vaadin.flow.component.html.Hr;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import com.vaadin.flow.router.RouteAlias;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import ru.dragonestia.picker.api.exception.ApiException;
|
||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||
@ -14,14 +14,13 @@ import ru.dragonestia.picker.cp.component.NavPath;
|
||||
import ru.dragonestia.picker.cp.component.NodeList;
|
||||
import ru.dragonestia.picker.cp.component.RegisterNode;
|
||||
|
||||
@Log4j2
|
||||
@Getter
|
||||
@PermitAll
|
||||
@PageTitle("Nodes")
|
||||
@RouteAlias(value = "/", layout = MainLayout.class)
|
||||
@Route(value = "/nodes", layout = MainLayout.class)
|
||||
public class NodesPage extends VerticalLayout {
|
||||
|
||||
private final NodeRepository nodeRepository;
|
||||
private final RegisterNode registerNode;
|
||||
private final NodeList nodeList;
|
||||
|
||||
public NodesPage(@Autowired RoomPickerClient client) {
|
||||
@ -29,7 +28,7 @@ public class NodesPage extends VerticalLayout {
|
||||
this.nodeRepository = client.getNodeRepository();
|
||||
|
||||
add(NavPath.rootNodes());
|
||||
add(registerNode = createRegisterNodeElement());
|
||||
add(createRegisterNodeElement());
|
||||
add(new Hr());
|
||||
add(nodeList = createNodeListElement());
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||
import ru.dragonestia.picker.api.model.node.INode;
|
||||
@ -31,6 +32,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@PermitAll
|
||||
@PageTitle("Room details")
|
||||
@Route(value = "/nodes/:nodeId/rooms/:roomId", layout = MainLayout.class)
|
||||
public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
||||
@ -40,7 +42,6 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
||||
|
||||
private INode node;
|
||||
private ResponseRoom room;
|
||||
private AddUsers addUsers;
|
||||
private UserList userList;
|
||||
private Button lockRoomButton;
|
||||
private VerticalLayout roomInfo;
|
||||
@ -58,7 +59,7 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
||||
add(new H2("Room details"));
|
||||
printRoomDetails();
|
||||
add(new Hr());
|
||||
add(addUsers = new AddUsers(room, (users, ignoreLimitation) -> appendUsers(room, users, ignoreLimitation)));
|
||||
add(new AddUsers(room, (users, ignoreLimitation) -> appendUsers(room, users, ignoreLimitation)));
|
||||
add(new Hr());
|
||||
add(new H2("Users"));
|
||||
add(userList = new UserList(room, client.getUserRepository()));
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package ru.dragonestia.picker.cp.page;
|
||||
|
||||
import com.vaadin.flow.component.Html;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.grid.ColumnTextAlign;
|
||||
@ -12,6 +11,7 @@ import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||
import ru.dragonestia.picker.api.model.room.RoomDetails;
|
||||
@ -21,11 +21,11 @@ import ru.dragonestia.picker.api.repository.query.user.FindRoomsLinkedWithUser;
|
||||
import ru.dragonestia.picker.cp.component.RefreshableTable;
|
||||
import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@PermitAll
|
||||
@PageTitle("User details")
|
||||
@Route(value = "/users/:userId", layout = MainLayout.class)
|
||||
public class UserDetailsPage extends VerticalLayout implements BeforeEnterObserver, RefreshableTable {
|
||||
@ -34,7 +34,6 @@ public class UserDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
||||
private final RouteParamsExtractor paramsExtractor;
|
||||
private IUser user;
|
||||
private Grid<ShortResponseRoom> gridRooms;
|
||||
private List<ShortResponseRoom> cachedRooms = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public void beforeEnter(BeforeEnterEvent event) {
|
||||
@ -79,13 +78,9 @@ public class UserDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
||||
return grid;
|
||||
}
|
||||
|
||||
private Html createComponent(String defaultValue) {
|
||||
return new Html("<span>" + defaultValue + "</span>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
cachedRooms = client.getUserRepository()
|
||||
List<ShortResponseRoom> cachedRooms = client.getUserRepository()
|
||||
.findRoomsLinkedWithUser(FindRoomsLinkedWithUser.withAllDetails(user.getIdentifierObject()));
|
||||
gridRooms.setItems(cachedRooms);
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import ru.dragonestia.picker.api.impl.RoomPickerClient;
|
||||
import ru.dragonestia.picker.api.model.user.IUser;
|
||||
@ -26,6 +27,7 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@PermitAll
|
||||
@PageTitle("Search users")
|
||||
@Route(value = "/users", layout = MainLayout.class)
|
||||
public class UserSearchPage extends VerticalLayout implements RefreshableTable {
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package ru.dragonestia.picker.cp.page.plug;
|
||||
|
||||
import com.vaadin.flow.component.html.H1;
|
||||
import com.vaadin.flow.component.html.Paragraph;
|
||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.ErrorParameter;
|
||||
import com.vaadin.flow.router.HasErrorParameter;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import ru.dragonestia.picker.api.impl.exception.NotEnoughPermissions;
|
||||
|
||||
public class NotEnoughPermissionsPlug extends ErrorPlug implements HasErrorParameter<NotEnoughPermissions> {
|
||||
|
||||
@Override
|
||||
public int setErrorParameter(BeforeEnterEvent event, ErrorParameter<NotEnoughPermissions> parameter) {
|
||||
return render(this);
|
||||
}
|
||||
|
||||
public static int render(ErrorPlug plug) {
|
||||
plug.add(new H1("Access denied"));
|
||||
plug.add(new Paragraph("You do not have sufficient permissions to access this page"));
|
||||
return HttpServletResponse.SC_FORBIDDEN;
|
||||
}
|
||||
}
|
||||
@ -17,3 +17,12 @@ services:
|
||||
GF_SECURITY_ADMIN_PASSWORD: admin
|
||||
volumes:
|
||||
- "./appdata/grafana:/var/lib/grafana/"
|
||||
|
||||
server:
|
||||
image: openjdk:17-oracle
|
||||
ports:
|
||||
- "8080:8080"
|
||||
working_dir: "/app"
|
||||
volumes:
|
||||
- "./appdata/server:/app"
|
||||
entrypoint: "java -jar -Dspring.profiles.active='test' server.jar"
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package ru.dragonestia.picker.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ru.dragonestia.picker.api.model.account.ResponseAccount;
|
||||
import ru.dragonestia.picker.model.Account;
|
||||
import ru.dragonestia.picker.service.AccountService;
|
||||
@ -21,4 +21,13 @@ public class AccountsController {
|
||||
var account = (Account) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
return account.toResponseObject();
|
||||
}
|
||||
|
||||
@GetMapping("/{accountId}")
|
||||
ResponseEntity<ResponseAccount> findAccount(@PathVariable String accountId) {
|
||||
try {
|
||||
return ResponseEntity.ok(accountService.loadUserByUsername(accountId).toResponseObject());
|
||||
} catch (UsernameNotFoundException ex) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,6 +61,11 @@ public class ExceptionHandlerController {
|
||||
return create(400, ex);
|
||||
}
|
||||
|
||||
@ExceptionHandler(AccountDoesNotExistsException.class)
|
||||
ResponseEntity<?> accountDoesNotExists(AccountDoesNotExistsException ex) {
|
||||
return create(404, ex);
|
||||
}
|
||||
|
||||
private ResponseEntity<ErrorResponse> create(int code, ApiException ex) {
|
||||
var details = new HashMap<String, String>();
|
||||
ex.appendDetailsToErrorResponse(details);
|
||||
|
||||
@ -9,6 +9,6 @@ public enum Permission implements GrantedAuthority {
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return name();
|
||||
return "ROLE_" + name();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package ru.dragonestia.picker.service;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import ru.dragonestia.picker.model.Account;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -17,4 +18,7 @@ public interface AccountService extends UserDetailsService {
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
void removeAccount(@NotNull Account account);
|
||||
|
||||
@Override
|
||||
Account loadUserByUsername(String username) throws UsernameNotFoundException;
|
||||
}
|
||||
|
||||
@ -10,10 +10,11 @@ import ru.dragonestia.picker.model.Account;
|
||||
import ru.dragonestia.picker.model.Permission;
|
||||
import ru.dragonestia.picker.service.AccountService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@ -26,7 +27,7 @@ public class AccountServiceImpl implements AccountService {
|
||||
@PostConstruct
|
||||
void init() {
|
||||
var account = createNewAccount("admin", "qwerty123");
|
||||
account.setAuthorities(Set.of(Permission.ADMIN));
|
||||
account.setAuthorities(Arrays.stream(Permission.values()).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
public @NotNull Account createNewAccount(@NotNull String username, @NotNull String password) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user