[New] Added supported language as rest endpoint

Can now be configured in StrolchConfiguration.xml:
<supportedLanguages>
    <language locale="en" name="English"/>
    <language locale="de" name="Deutsch"/>
    <language locale="fr" name="Français"/>
</supportedLanguages>
This commit is contained in:
Robert von Burg 2023-06-09 16:07:08 +02:00
parent 731cea462c
commit 3b6a94ace5
Signed by: eitch
GPG Key ID: 75DB9C85C74331F7
7 changed files with 235 additions and 150 deletions

View File

@ -15,22 +15,27 @@
*/
package li.strolch.runtime.configuration;
import static li.strolch.runtime.configuration.ConfigurationTags.*;
import li.strolch.model.Locator;
import li.strolch.model.Locator.LocatorBuilder;
import li.strolch.model.Tags;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.File;
import java.text.MessageFormat;
import java.util.*;
import li.strolch.model.Locator;
import li.strolch.model.Locator.LocatorBuilder;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.StringHelper;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import static li.strolch.runtime.configuration.ConfigurationTags.*;
public class ConfigurationSaxParser extends DefaultHandler {
private static final Logger logger = LoggerFactory.getLogger(ConfigurationSaxParser.class);
private final String environment;
private String currentEnvironment;
@ -76,53 +81,53 @@ public class ConfigurationSaxParser extends DefaultHandler {
Locator locator = this.locatorBuilder.build();
switch (locator.toString()) {
case STROLCH_CONFIGURATION_ENV -> {
String env = attributes.getValue(ID);
DBC.PRE.assertNotEmpty("attribute 'id' must be set on element 'env'", env);
if (this.envBuilders.containsKey(env)) {
String msg = "Environment {0} already exists!";
throw new IllegalStateException(MessageFormat.format(msg, env));
case STROLCH_CONFIGURATION_ENV -> {
String env = attributes.getValue(ID);
DBC.PRE.assertNotEmpty("attribute 'id' must be set on element 'env'", env);
if (this.envBuilders.containsKey(env)) {
String msg = "Environment {0} already exists!";
throw new IllegalStateException(MessageFormat.format(msg, env));
}
this.currentEnvironment = env;
ConfigurationBuilder newEnvBuilder = new ConfigurationBuilder();
newEnvBuilder.runtimeBuilder().setEnvironment(this.currentEnvironment);
this.envBuilders.put(env, newEnvBuilder);
}
this.currentEnvironment = env;
ConfigurationBuilder newEnvBuilder = new ConfigurationBuilder();
newEnvBuilder.runtimeBuilder().setEnvironment(this.currentEnvironment);
this.envBuilders.put(env, newEnvBuilder);
}
case STROLCH_CONFIGURATION_ENV_RUNTIME -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
RuntimeHandler runtimeHandler = new RuntimeHandler(configurationBuilder, locator);
this.delegateHandlers.push(runtimeHandler);
case STROLCH_CONFIGURATION_ENV_RUNTIME -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
RuntimeHandler runtimeHandler = new RuntimeHandler(configurationBuilder, locator);
this.delegateHandlers.push(runtimeHandler);
}
}
}
case STROLCH_CONFIGURATION_ENV_RUNTIME_PROPERTIES -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
PropertiesHandler runtimePropertiesHandler = new PropertiesHandler(configurationBuilder, locator);
this.delegateHandlers.push(runtimePropertiesHandler);
configurationBuilder.setPropertyBuilder(configurationBuilder.runtimeBuilder());
case STROLCH_CONFIGURATION_ENV_RUNTIME_PROPERTIES -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
PropertiesHandler runtimePropertiesHandler = new PropertiesHandler(configurationBuilder, locator);
this.delegateHandlers.push(runtimePropertiesHandler);
configurationBuilder.setPropertyBuilder(configurationBuilder.runtimeBuilder());
}
}
}
case STROLCH_CONFIGURATION_ENV_COMPONENT -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
configurationBuilder.nextComponentBuilder();
ComponentHandler componentHandler = new ComponentHandler(configurationBuilder, locator);
this.delegateHandlers.push(componentHandler);
case STROLCH_CONFIGURATION_ENV_COMPONENT -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
configurationBuilder.nextComponentBuilder();
ComponentHandler componentHandler = new ComponentHandler(configurationBuilder, locator);
this.delegateHandlers.push(componentHandler);
}
}
}
case STROLCH_CONFIGURATION_ENV_COMPONENT_PROPERTIES -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
PropertiesHandler componentPropertiesHandler = new PropertiesHandler(configurationBuilder, locator);
this.delegateHandlers.push(componentPropertiesHandler);
configurationBuilder.setPropertyBuilder(configurationBuilder.componentBuilder());
case STROLCH_CONFIGURATION_ENV_COMPONENT_PROPERTIES -> {
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
PropertiesHandler componentPropertiesHandler = new PropertiesHandler(configurationBuilder, locator);
this.delegateHandlers.push(componentPropertiesHandler);
configurationBuilder.setPropertyBuilder(configurationBuilder.componentBuilder());
}
}
default -> {
if (!this.delegateHandlers.isEmpty())
this.delegateHandlers.peek().startElement(uri, localName, qName, attributes);
}
}
default -> {
if (!this.delegateHandlers.isEmpty())
this.delegateHandlers.peek().startElement(uri, localName, qName, attributes);
}
}
}
@ -146,26 +151,26 @@ public class ConfigurationSaxParser extends DefaultHandler {
switch (locator.toString()) {
case STROLCH_CONFIGURATION_ENV:
break;
case STROLCH_CONFIGURATION_ENV:
break;
case STROLCH_CONFIGURATION_ENV_RUNTIME, STROLCH_CONFIGURATION_ENV_COMPONENT:
if (isRequiredEnv(this.currentEnvironment)) {
assertExpectedLocator(locator, this.delegateHandlers.pop().getLocator());
}
break;
case STROLCH_CONFIGURATION_ENV_RUNTIME, STROLCH_CONFIGURATION_ENV_COMPONENT:
if (isRequiredEnv(this.currentEnvironment)) {
assertExpectedLocator(locator, this.delegateHandlers.pop().getLocator());
}
break;
case STROLCH_CONFIGURATION_ENV_RUNTIME_PROPERTIES, STROLCH_CONFIGURATION_ENV_COMPONENT_PROPERTIES:
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
assertExpectedLocator(locator, this.delegateHandlers.pop().getLocator());
configurationBuilder.setPropertyBuilder(null);
}
break;
case STROLCH_CONFIGURATION_ENV_RUNTIME_PROPERTIES, STROLCH_CONFIGURATION_ENV_COMPONENT_PROPERTIES:
if (isRequiredEnv(this.currentEnvironment)) {
ConfigurationBuilder configurationBuilder = getEnvBuilder(this.currentEnvironment);
assertExpectedLocator(locator, this.delegateHandlers.pop().getLocator());
configurationBuilder.setPropertyBuilder(null);
}
break;
default:
if (!this.delegateHandlers.isEmpty())
this.delegateHandlers.peek().endElement(uri, localName, qName);
default:
if (!this.delegateHandlers.isEmpty())
this.delegateHandlers.peek().endElement(uri, localName, qName);
}
this.locatorBuilder.removeLast();
@ -212,6 +217,15 @@ public class ConfigurationSaxParser extends DefaultHandler {
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if (qName.equals(APPLICATION_NAME)) {
this.valueBuffer = new StringBuilder();
} else if (qName.equals(LANGUAGE)) {
String locale = attributes.getValue(Tags.Json.LOCALE);
String name = attributes.getValue(Tags.Json.NAME);
if (StringHelper.isEmpty(locale) || StringHelper.isEmpty(name)) {
logger.error("Ignoring invalid supported language definition with empty values!");
} else {
SupportedLanguage language = new SupportedLanguage(locale, name);
configurationBuilder.runtimeBuilder.addSupportedLanguage(language);
}
}
}
@ -234,35 +248,35 @@ public class ConfigurationSaxParser extends DefaultHandler {
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
switch (qName) {
case NAME, API, IMPL, DEPENDS -> this.valueBuffer = new StringBuilder();
default -> {
// no nothing for others, as only these are text elements
}
case NAME, API, IMPL, DEPENDS -> this.valueBuffer = new StringBuilder();
default -> {
// no nothing for others, as only these are text elements
}
}
}
@Override
public void endElement(String uri, String localName, String qName) {
switch (qName) {
case NAME -> {
String name = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setName(name);
this.valueBuffer = null;
}
case API -> {
String api = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setApi(api);
this.valueBuffer = null;
}
case IMPL -> {
String impl = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setImpl(impl);
}
case DEPENDS -> {
String depends = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().addDependency(depends);
}
default -> throw new IllegalStateException("Unexpected value: " + qName);
case NAME -> {
String name = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setName(name);
this.valueBuffer = null;
}
case API -> {
String api = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setApi(api);
this.valueBuffer = null;
}
case IMPL -> {
String impl = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().setImpl(impl);
}
case DEPENDS -> {
String depends = this.valueBuffer.toString();
this.configurationBuilder.componentBuilder().addDependency(depends);
}
default -> throw new IllegalStateException("Unexpected value: " + qName);
}
}
@ -352,8 +366,7 @@ public class ConfigurationSaxParser extends DefaultHandler {
/**
* Merge the given {@link ConfigurationBuilder ConfigurationBuilder's} values into this configuration builder
*
* @param otherConfBuilder
* the {@link ConfigurationBuilder} to be merged into this
* @param otherConfBuilder the {@link ConfigurationBuilder} to be merged into this
*/
public void merge(ConfigurationBuilder otherConfBuilder) {
@ -363,9 +376,10 @@ public class ConfigurationSaxParser extends DefaultHandler {
RuntimeBuilder other = otherConfBuilder.runtimeBuilder;
if (StringHelper.isNotEmpty(other.getApplicationName()))
thisRuntime.setApplicationName(other.getApplicationName());
if (!other.getProperties().isEmpty()) {
if (!other.getProperties().isEmpty())
thisRuntime.getProperties().putAll(other.getProperties());
}
if (!other.supportedLanguages.isEmpty())
thisRuntime.supportedLanguages.addAll(other.supportedLanguages);
if (!otherConfBuilder.componentBuilders.isEmpty()) {
Map<String, ComponentBuilder> thisComponentBuilders = new HashMap<>();
@ -412,6 +426,12 @@ public class ConfigurationSaxParser extends DefaultHandler {
private String applicationName;
private String environment;
private final Set<SupportedLanguage> supportedLanguages;
public RuntimeBuilder() {
this.supportedLanguages = new HashSet<>();
}
public String getApplicationName() {
return this.applicationName;
}
@ -420,9 +440,17 @@ public class ConfigurationSaxParser extends DefaultHandler {
return this.environment;
}
public void addSupportedLanguage(SupportedLanguage language) {
this.supportedLanguages.add(language);
}
public Set<SupportedLanguage> getSupportedLanguages() {
return this.supportedLanguages;
}
public RuntimeConfiguration build(File configPathF, File dataPathF, File tempPathF) {
return new RuntimeConfiguration(this.applicationName, this.environment, getProperties(), configPathF,
dataPathF, tempPathF);
dataPathF, tempPathF, this.supportedLanguages);
}
public RuntimeBuilder setApplicationName(String applicationName) {

View File

@ -29,6 +29,7 @@ public class ConfigurationTags {
public static final String STROLCH_CONFIGURATION_ENV = "StrolchConfiguration/env";
public static final String APPLICATION_NAME = "applicationName";
public static final String LANGUAGE = "language";
public static final String ID = "id";
public static final String DEPENDS = "depends";
public static final String IMPL = "impl";

View File

@ -19,6 +19,7 @@ import java.io.File;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class RuntimeConfiguration extends AbstractionConfiguration {
@ -33,9 +34,10 @@ public class RuntimeConfiguration extends AbstractionConfiguration {
private final File tempPath;
private final Locale locale;
private final Set<SupportedLanguage> supportedLanguages;
public RuntimeConfiguration(String applicationName, String environment, Map<String, String> configurationValues,
File configPathF, File dataPathF, File tempPathF) {
File configPathF, File dataPathF, File tempPathF, Set<SupportedLanguage> supportedLanguages) {
super(RUNTIME, configurationValues);
// config path: readable directory
@ -67,6 +69,7 @@ public class RuntimeConfiguration extends AbstractionConfiguration {
this.tempPath = tempPathF;
this.locale = new Locale(getString(PROP_LOCALE, Locale.getDefault().toLanguageTag()));
this.supportedLanguages = supportedLanguages;
}
public String getApplicationName() {
@ -93,6 +96,10 @@ public class RuntimeConfiguration extends AbstractionConfiguration {
return this.locale;
}
public Set<SupportedLanguage> getSupportedLanguages() {
return this.supportedLanguages;
}
public String getTimezone() {
return getString(RuntimeConfiguration.PROP_TIMEZONE, System.getProperty("user.timezone"));
}
@ -100,12 +107,10 @@ public class RuntimeConfiguration extends AbstractionConfiguration {
/**
* Returns the file in the config directory of the root of the application
*
* @param context
* short name to define who requires this file for error handling
* @param fileName
* the relative name of the config file to return
* @param checkExists
* if true, then an exception is thrown, using the context as info, if the config file does not exist
* @param context short name to define who requires this file for error handling
* @param fileName the relative name of the config file to return
* @param checkExists if true, then an exception is thrown, using the context as info, if the config file does not
* exist
*
* @return the file in the config directory of the root of the application
*/
@ -122,12 +127,10 @@ public class RuntimeConfiguration extends AbstractionConfiguration {
/**
* Returns the file in the data directory of the root of the application
*
* @param context
* short name to define who requires this file for error handling
* @param fileName
* the relative name of the data file to return
* @param checkExists
* if true, then an exception is thrown, using the context as info, if the data file does not exist
* @param context short name to define who requires this file for error handling
* @param fileName the relative name of the data file to return
* @param checkExists if true, then an exception is thrown, using the context as info, if the data file does not
* exist
*
* @return the file in the data directory of the root of the application
*/
@ -144,12 +147,10 @@ public class RuntimeConfiguration extends AbstractionConfiguration {
/**
* Returns the directory in the data directory of the root of the application
*
* @param context
* short name to define who requires this directory for error handling
* @param dirName
* the relative name of the data directory to return
* @param checkExists
* if true, then an exception is thrown, using the context as info, if the data directory does not exist
* @param context short name to define who requires this directory for error handling
* @param dirName the relative name of the data directory to return
* @param checkExists if true, then an exception is thrown, using the context as info, if the data directory does
* not exist
*
* @return the directory in the data directory of the root of the application
*/

View File

@ -0,0 +1,19 @@
package li.strolch.runtime.configuration;
public class SupportedLanguage {
private final String locale;
private final String name;
public SupportedLanguage(String locale, String name) {
this.locale = locale;
this.name = name;
}
public String getLocale() {
return locale;
}
public String getName() {
return name;
}
}

View File

@ -43,6 +43,7 @@ public class StrolchRestfulClasses {
restfulClasses.add(VersionQuery.class);
restfulClasses.add(ModelQuery.class);
restfulClasses.add(EnumQuery.class);
restfulClasses.add(Languages.class);
restfulClasses.add(OperationsLogResource.class);
// privilege

View File

@ -0,0 +1,45 @@
package li.strolch.rest.endpoint;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import li.strolch.model.Tags;
import li.strolch.rest.RestfulStrolchComponent;
import li.strolch.runtime.configuration.SupportedLanguage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import static li.strolch.utils.helper.ExceptionHelper.getRootCauseMessage;
@Path("strolch/languages")
public class Languages {
private static final Logger logger = LoggerFactory.getLogger(Languages.class);
@GET
@Path("supported")
@Produces(MediaType.APPLICATION_JSON)
public Response getSupportedLanguages() {
try {
Set<SupportedLanguage> supportedLanguages = RestfulStrolchComponent.getInstance().getAgent()
.getStrolchConfiguration().getRuntimeConfiguration().getSupportedLanguages();
JsonArray result = supportedLanguages.stream().map(language -> {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(Tags.Json.LOCALE, language.getLocale());
jsonObject.addProperty(Tags.Json.NAME, language.getName());
return jsonObject;
}).collect(JsonArray::new, JsonArray::add, JsonArray::addAll);
return Response.ok().entity(result.toString()).build();
} catch (Exception e) {
logger.error("Failed to get supported languages: " + e.getMessage(), e);
return Response.serverError().entity(getRootCauseMessage(e)).build();
}
}
}

View File

@ -85,14 +85,14 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
paths.add("strolch/authentication");
paths.add("strolch/authentication/sso");
paths.add("strolch/version");
paths.add("strolch/languages");
return paths;
}
/**
* Validates if the path for the given request is for an unsecured path, i.e. no authorization is required
*
* @param requestContext
* the request context
* @param requestContext the request context
*
* @return true if the request context is for an unsecured path, false if not, meaning authorization must be
* validated
@ -115,8 +115,8 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) {
String remoteIp = getRemoteIp(this.request);
logger.info("Remote IP: " + remoteIp + ": " + requestContext.getMethod() + " " + requestContext.getUriInfo()
.getRequestUri());
logger.info("Remote IP: " + remoteIp + ": " + requestContext.getMethod() + " " +
requestContext.getUriInfo().getRequestUri());
try {
@ -128,22 +128,19 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
} catch (StrolchNotAuthenticatedException e) {
logger.error(e.getMessage());
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User is not authenticated!")
.build());
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User is not authenticated!").build());
} catch (StrolchAccessDeniedException e) {
logger.error(e.getMessage());
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User is not authorized!")
.build());
requestContext.abortWith(
Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User is not authorized!").build());
} catch (Exception e) {
logger.error(e.getMessage());
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User cannot access the resource.")
.build());
requestContext.abortWith(
Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("User cannot access the resource.").build());
}
}
@ -182,10 +179,8 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
* Sub classes should override this method and first call super. If the return value is non-null, then further
* validation can be performed
*
* @param requestContext
* the request context for the secured path
* @param remoteIp
* the remote IP
* @param requestContext the request context for the secured path
* @param remoteIp the remote IP
*
* @return the certificate for the validated session, or null, of the request is aborted to no missing or invalid
* authorization token
@ -216,10 +211,9 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
if (isEmpty(sessionId)) {
logger.error(
"No Authorization header or cookie on request to URL " + requestContext.getUriInfo().getPath());
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Missing Authorization!")
.build());
requestContext.abortWith(
Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Missing Authorization!").build());
return null;
}
@ -231,10 +225,9 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
if (!getRestful().isBasicAuthEnabled()) {
logger.error("Basic Auth is not available for URL " + requestContext.getUriInfo().getPath());
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Basic Auth not available")
.build());
requestContext.abortWith(
Response.status(Response.Status.FORBIDDEN).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Basic Auth not available").build());
return null;
}
@ -242,10 +235,9 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
basicAuth = new String(Base64.getDecoder().decode(basicAuth.getBytes()), StandardCharsets.UTF_8);
String[] parts = basicAuth.split(":");
if (parts.length != 2) {
requestContext.abortWith(Response.status(Response.Status.BAD_REQUEST)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Invalid Basic Authorization!")
.build());
requestContext.abortWith(
Response.status(Response.Status.BAD_REQUEST).header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Invalid Basic Authorization!").build());
return null;
}
@ -266,12 +258,10 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
Certificate certificate = sessionHandler.validate(sessionId, remoteIp);
if (certificate.getUsage() == Usage.SET_PASSWORD) {
if (!requestContext.getUriInfo()
.getMatchedURIs()
if (!requestContext.getUriInfo().getMatchedURIs()
.contains("strolch/privilege/users/" + certificate.getUsername() + "/password")) {
requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
.entity("Can only set password!")
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN).entity("Can only set password!")
.build());
return null;
}