2727import java .util .HashMap ;
2828import java .util .Map ;
2929import java .util .Objects ;
30+ import java .util .regex .Matcher ;
31+ import java .util .regex .Pattern ;
3032
3133/**
3234 * Represents a "wrapped reference" to a user-owned secret that holds an identifier to retrieve
@@ -56,6 +58,43 @@ public class UserSecretReference {
5658 @ JsonProperty (value = "referencePayload" )
5759 private final Map <String , String > referencePayload ;
5860
61+ private static final String URN_SCHEME = "urn" ;
62+ private static final String URN_NAMESPACE = "polaris-secret" ;
63+ private static final String SECRET_MANAGER_TYPE_REGEX = "([a-zA-Z0-9_-]+)" ;
64+ private static final String TYPE_SPECIFIC_IDENTIFIER_REGEX =
65+ "([a-zA-Z0-9_-]+(?::[a-zA-Z0-9_-]+)*)" ;
66+
67+ /**
68+ * Precompiled regex pattern for validating the secret manager type and type-specific identifier.
69+ */
70+ private static final Pattern SECRET_MANAGER_TYPE_PATTERN =
71+ Pattern .compile ("^" + SECRET_MANAGER_TYPE_REGEX + "$" );
72+
73+ private static final Pattern TYPE_SPECIFIC_IDENTIFIER_PATTERN =
74+ Pattern .compile ("^" + TYPE_SPECIFIC_IDENTIFIER_REGEX + "$" );
75+
76+ /**
77+ * Precompiled regex pattern for validating and parsing UserSecretReference URNs. Expected format:
78+ * urn:polaris-secret:<secret-manager-type>:<identifier1>(:<identifier2>:...).
79+ *
80+ * <p>Groups:
81+ *
82+ * <p>Group 1: secret-manager-type (alphanumeric, hyphens, underscores).
83+ *
84+ * <p>Group 2: type-specific-identifier (one or more colon-separated alphanumeric components).
85+ */
86+ private static final Pattern URN_PATTERN =
87+ Pattern .compile (
88+ "^"
89+ + URN_SCHEME
90+ + ":"
91+ + URN_NAMESPACE
92+ + ":"
93+ + SECRET_MANAGER_TYPE_REGEX
94+ + ":"
95+ + TYPE_SPECIFIC_IDENTIFIER_REGEX
96+ + "$" );
97+
5998 /**
6099 * @param urn A string which should be self-sufficient to retrieve whatever secret material that
61100 * is stored in the remote secret store and also to identify an implementation of the
@@ -70,26 +109,83 @@ public class UserSecretReference {
70109 public UserSecretReference (
71110 @ JsonProperty (value = "urn" , required = true ) @ Nonnull String urn ,
72111 @ JsonProperty (value = "referencePayload" ) @ Nullable Map <String , String > referencePayload ) {
73- // TODO: Add better/standardized parsing and validation of URN syntax
74112 Preconditions .checkArgument (
75- urn .startsWith ("urn:polaris-secret:" ) && urn .split (":" ).length >= 4 ,
76- "Invalid secret URN '%s'; must be of the form "
77- + "'urn:polaris-secret:<secret-manager-type>:<type-specific-identifier>'" ,
78- urn );
113+ urnIsValid (urn ),
114+ "Invalid secret URN: " + urn + "; must be of the form: " + URN_PATTERN .toString ());
79115 this .urn = urn ;
80116 this .referencePayload = Objects .requireNonNullElse (referencePayload , new HashMap <>());
81117 }
82118
119+ /**
120+ * Validates whether the given URN string matches the expected format for UserSecretReference
121+ * URNs.
122+ *
123+ * @param urn The URN string to validate.
124+ * @return true if the URN is valid, false otherwise.
125+ */
126+ private static boolean urnIsValid (@ Nonnull String urn ) {
127+ return urn .trim ().isEmpty () ? false : URN_PATTERN .matcher (urn ).matches ();
128+ }
129+
130+ /**
131+ * Builds a URN string from the given secret manager type and type-specific identifier. Validates
132+ * the inputs to ensure they conform to the expected pattern.
133+ *
134+ * @param secretManagerType The secret manager type (alphanumeric, hyphens, underscores).
135+ * @param typeSpecificIdentifier The type-specific identifier (colon-separated alphanumeric
136+ * components).
137+ * @return The constructed URN string.
138+ */
139+ @ Nonnull
140+ public static String buildUrnString (
141+ @ Nonnull String secretManagerType , @ Nonnull String typeSpecificIdentifier ) {
142+
143+ Preconditions .checkArgument (
144+ !secretManagerType .trim ().isEmpty (), "Secret manager type cannot be empty" );
145+ Preconditions .checkArgument (
146+ SECRET_MANAGER_TYPE_PATTERN .matcher (secretManagerType ).matches (),
147+ "Invalid secret manager type '%s'; must contain only alphanumeric characters, hyphens, and underscores" ,
148+ secretManagerType );
149+
150+ Preconditions .checkArgument (
151+ !typeSpecificIdentifier .trim ().isEmpty (), "Type-specific identifier cannot be empty" );
152+ Preconditions .checkArgument (
153+ TYPE_SPECIFIC_IDENTIFIER_PATTERN .matcher (typeSpecificIdentifier ).matches (),
154+ "Invalid type-specific identifier '%s'; must be colon-separated alphanumeric components (hyphens and underscores allowed)" ,
155+ typeSpecificIdentifier );
156+
157+ return URN_SCHEME
158+ + ":"
159+ + URN_NAMESPACE
160+ + ":"
161+ + secretManagerType
162+ + ":"
163+ + typeSpecificIdentifier ;
164+ }
165+
83166 /**
84167 * Since UserSecretReference objects are specific to UserSecretManager implementations, the
85168 * "secret-manager-type" portion of the URN should be used to validate that a URN is valid for a
86169 * given implementation and to dispatch to the correct implementation at runtime if multiple
87170 * concurrent implementations are possible in a given runtime environment.
88171 */
89172 @ JsonIgnore
90- public String getUserSecretManagerTypeFromUrn () {
91- // TODO: Add better/standardized parsing and validation of URN syntax
92- return urn .split (":" )[2 ];
173+ public String getUserSecretManagerType () {
174+ Matcher matcher = URN_PATTERN .matcher (urn );
175+ Preconditions .checkState (matcher .matches (), "Invalid secret URN: " + urn );
176+ return matcher .group (1 );
177+ }
178+
179+ /**
180+ * Returns the type-specific identifier from the URN. Since the format is specific to the
181+ * UserSecretManager implementation, this method does not validate the identifier. It is the
182+ * responsibility of the caller to validate it.
183+ */
184+ @ JsonIgnore
185+ public String getTypeSpecificIdentifier () {
186+ Matcher matcher = URN_PATTERN .matcher (urn );
187+ Preconditions .checkState (matcher .matches (), "Invalid secret URN: " + urn );
188+ return matcher .group (2 );
93189 }
94190
95191 public @ Nonnull String getUrn () {
0 commit comments