001/*- 002 * #%L 003 * Smile CDR - CDR 004 * %% 005 * Copyright (C) 2016 - 2025 Smile CDR, Inc. 006 * %% 007 * All rights reserved. 008 * #L% 009 */ 010package ca.cdr.api.security.permission; 011 012import jakarta.annotation.Nonnull; 013 014import java.util.Optional; 015 016import static org.apache.commons.lang3.StringUtils.isNotBlank; 017 018/** 019 * A role name. 020 * Accepts only uppercase letters and single underscores between letters. 021 */ 022public final class RoleName extends PermissionArgumentValue implements PermissionArgumentFormat.IRoleNameRestriction { 023 // final for optimizer 024 025 private static final String ROLE_NAME_PATTERN = "^[A-Z]+(_[A-Z]+)*$"; 026 027 private final String myRoleName; 028 029 RoleName(String theRoleName) { 030 myRoleName = theRoleName; 031 } 032 033 /** 034 * Validates that a role name contains only uppercase letters and single underscores between letters. 035 * <p> 036 * Valid examples: {@code MDM_UI_DATA_STEWARD}, {@code MY_ROLE}, {@code A} 037 * <p> 038 * Invalid examples: {@code myRole} (lowercase), {@code ROLE__SALES} (consecutive underscores), 039 * {@code _ADMIN} (leading underscore), {@code ADMIN_} (trailing underscore), 040 * {@code ROLE123} (numbers), {@code ROLE-ADMIN} (special characters) 041 * 042 * @param theRoleName the role name to validate 043 * @return true if the role name is valid, false otherwise 044 */ 045 public static boolean isValidRoleName(String theRoleName) { 046 return isNotBlank(theRoleName) && theRoleName.matches(ROLE_NAME_PATTERN); 047 } 048 049 /** 050 * Validates that a role name contains only uppercase letters and single underscores between letters. 051 * Throws an exception if the role name is invalid. 052 * 053 * @param theRoleName the role name to validate 054 * @param theRoleType the type of role (e.g., "APP_ROLE", "CUSTOM_ROLE") for error messages 055 * @throws IllegalArgumentException if the role name is invalid 056 */ 057 public static void validateRoleName(String theRoleName, String theRoleType) { 058 if (!isValidRoleName(theRoleName)) { 059 throw new IllegalArgumentException(theRoleType 060 + " name must contain only uppercase letters and single underscores between letters. Invalid role name: " 061 + theRoleName); 062 } 063 } 064 065 @Override 066 public String getRoleName() { 067 return myRoleName; 068 } 069 070 @Override 071 @Nonnull 072 String getStringValue() { 073 return myRoleName; 074 } 075 076 static final class Format implements PermissionArgumentFormat<RoleName> { 077 078 @Override 079 public Class<RoleName> getType() { 080 return RoleName.class; 081 } 082 083 @Override 084 public @Nonnull Optional<RoleName> parse(String theArgumentString, @Nonnull IdBuilder theIdBuilder) { 085 if (isValidRoleName(theArgumentString)) { 086 return Optional.of(new RoleName(theArgumentString)); 087 } 088 089 return Optional.empty(); 090 } 091 092 @Override 093 public @Nonnull String format(@Nonnull RoleName theArgument) { 094 return theArgument.getStringValue(); 095 } 096 } 097}