Hi All, I have a requirement to process the auth response before I give it to client.
i.e Post to successful authentication I would like to make a REST call to
validate the user against 3rd party and return the AuthResposne.
As part of this I have written a new CustomAuthHandlerConfig.java which is
as similar as LdapAuthenticationConfiguration in CAS.
Getting below errors during cas-overlay build.
*[ERROR] COMPILATION ERROR : *
*[INFO] -------------------------------------------------------------*
*[ERROR]
/home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[22,45]
package org.apereo.cas.authentication.support does not exist*
*[ERROR]
/home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[23,45]
package org.apereo.cas.authentication.support does not exist*
*[ERROR]
/home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[24,45]
package org.apereo.cas.authentication.support does not exist*
*[ERROR]
/home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[33,27]
package org.apereo.cas.util does not exist*
*[ERROR]
/home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[34,27]
package org.apereo.cas.util does not exist*
Added Dependency as:
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication-api</artifactId>
<version>${cas.version}</version>
</dependency>
Attached related files for the same.
--
- Website: https://apereo.github.io/cas
- Gitter Chatroom: https://gitter.im/apereo/cas
- List Guidelines: https://goo.gl/1VRrw7
- Contributions: https://goo.gl/mh7qDG
---
You received this message because you are subscribed to the Google Groups "CAS
Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/a/apereo.org/d/msgid/cas-user/61ee3023-926d-4d28-b5a8-1c7203fa59b0%40apereo.org.
[INFO] Scanning for projects... [INFO] [INFO] ---------------------< org.apereo.cas:cas-overlay >--------------------- [INFO] Building cas-overlay 1.0 [INFO] --------------------------------[ war ]--------------------------------- [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ cas-overlay --- [INFO] Deleting /home/username/cas-workspace/cas-overlay-template/target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ cas-overlay --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory /home/username/cas-workspace/cas-overlay-template/src/main/resources [INFO] [INFO] --- maven-compiler-plugin:3.3:compile (default-compile) @ cas-overlay --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 3 source files to /home/username/cas-workspace/cas-overlay-template/target/classes [INFO] ------------------------------------------------------------- [ERROR] COMPILATION ERROR : [INFO] ------------------------------------------------------------- [ERROR] /home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[22,45] package org.apereo.cas.authentication.support does not exist [ERROR] /home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[23,45] package org.apereo.cas.authentication.support does not exist [ERROR] /home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[24,45] package org.apereo.cas.authentication.support does not exist [ERROR] /home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[33,27] package org.apereo.cas.util does not exist [ERROR] /home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[34,27] package org.apereo.cas.util does not exist [INFO] 5 errors [INFO] ------------------------------------------------------------- [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 5.848 s [INFO] Finished at: 2019-05-24T16:33:58+05:30 [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.3:compile (default-compile) on project cas-overlay: Compilation failure: Compilation failure: [ERROR] /home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[22,45] package org.apereo.cas.authentication.support does not exist [ERROR] /home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[23,45] package org.apereo.cas.authentication.support does not exist [ERROR] /home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[24,45] package org.apereo.cas.authentication.support does not exist [ERROR] /home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[33,27] package org.apereo.cas.util does not exist [ERROR] /home/username/cas-workspace/cas-overlay-template/src/main/java/com/pramati/cas/config/CustomAuthHandlerConfig.java:[34,27] package org.apereo.cas.util does not exist [ERROR] -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
package com.test.cas.config;
import java.time.Period;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.AuthenticationPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.CoreAuthenticationUtils;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalFactoryUtils;
import org.apereo.cas.authentication.principal.PrincipalNameTransformerUtils;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import org.apereo.cas.authentication.support.DefaultLdapAccountStateHandler;
import org.apereo.cas.authentication.support.OptionalWarningLdapAccountStateHandler;
import org.apereo.cas.authentication.support.RejectResultCodeLdapPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.support.password.DefaultPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.support.password.GroovyPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.support.password.PasswordEncoderUtils;
import org.apereo.cas.authentication.support.password.PasswordPolicyConfiguration;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.support.ldap.LdapAuthenticationProperties;
import org.apereo.cas.configuration.model.support.ldap.LdapPasswordPolicyProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.util.CollectionUtils;
import org.apereo.cas.util.LdapUtils;
import org.ldaptive.auth.AuthenticationResponse;
import org.ldaptive.auth.AuthenticationResponseHandler;
import org.ldaptive.auth.Authenticator;
import org.ldaptive.auth.ext.ActiveDirectoryAuthenticationResponseHandler;
import org.ldaptive.auth.ext.EDirectoryAuthenticationResponseHandler;
import org.ldaptive.auth.ext.FreeIPAAuthenticationResponseHandler;
import org.ldaptive.auth.ext.PasswordExpirationAuthenticationResponseHandler;
import org.ldaptive.auth.ext.PasswordPolicyAuthenticationResponseHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import com.google.common.collect.Multimap;
import com.test.cas.config.handlers.CustomAuthHandler;
@Configuration("ldapAuthenticationConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomAuthHandlerConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthHandlerConfig.class);
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("personDirectoryPrincipalResolver")
private PrincipalResolver personDirectoryPrincipalResolver;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@ConditionalOnMissingBean(name = "ldapPrincipalFactory")
@Bean
public PrincipalFactory ldapPrincipalFactory() {
return PrincipalFactoryUtils.newPrincipalFactory();
}
@Bean
public Collection<AuthenticationHandler> ldapAuthenticationHandlers() {
final Collection<AuthenticationHandler> handlers = new HashSet<>();
LOGGER.debug("PRAMATI - CAS - USING CUSTOM LDAP CONFIGURATION");
casProperties.getAuthn().getLdap()
.stream()
.filter(ldapInstanceConfigurationPredicate())
.forEach(l -> {
final Multimap<String, Object> multiMapAttributes =
CoreAuthenticationUtils.transformPrincipalAttributesListIntoMultiMap(l.getPrincipalAttributeList());
LOGGER.debug("Created and mapped principal attributes [{}] for [{}]...", multiMapAttributes, l.getLdapUrl());
LOGGER.debug("Creating LDAP authenticator for [{}] and baseDn [{}]", l.getLdapUrl(), l.getBaseDn());
final Authenticator authenticator = LdapUtils.newLdaptiveAuthenticator(l);
LOGGER.debug("Ldap authenticator configured with return attributes [{}] for [{}] and baseDn [{}]",
multiMapAttributes.keySet(), l.getLdapUrl(), l.getBaseDn());
LOGGER.debug("Creating LDAP password policy handling strategy for [{}]", l.getLdapUrl());
final AuthenticationPasswordPolicyHandlingStrategy strategy = createLdapPasswordPolicyHandlingStrategy(l);
LOGGER.debug("Creating LDAP authentication handler for [{}]", l.getLdapUrl());
final CustomAuthHandler handler = new CustomAuthHandler(l.getName(),
servicesManager, ldapPrincipalFactory(), l.getOrder(), authenticator, strategy);
handler.setCollectDnAttribute(l.isCollectDnAttribute());
final List<String> additionalAttributes = l.getAdditionalAttributes();
if (StringUtils.isNotBlank(l.getPrincipalAttributeId())) {
additionalAttributes.add(l.getPrincipalAttributeId());
}
if (StringUtils.isNotBlank(l.getPrincipalDnAttributeName())) {
handler.setPrincipalDnAttributeName(l.getPrincipalDnAttributeName());
}
handler.setAllowMultiplePrincipalAttributeValues(l.isAllowMultiplePrincipalAttributeValues());
handler.setAllowMissingPrincipalAttributeValue(l.isAllowMissingPrincipalAttributeValue());
handler.setPasswordEncoder(PasswordEncoderUtils.newPasswordEncoder(l.getPasswordEncoder()));
handler.setPrincipalNameTransformer(PrincipalNameTransformerUtils.newPrincipalNameTransformer(l.getPrincipalTransformation()));
if (StringUtils.isNotBlank(l.getCredentialCriteria())) {
LOGGER.debug("Ldap authentication for [{}] is filtering credentials by [{}]",
l.getLdapUrl(), l.getCredentialCriteria());
handler.setCredentialSelectionPredicate(CoreAuthenticationUtils.newCredentialSelectionPredicate(l.getCredentialCriteria()));
}
if (StringUtils.isBlank(l.getPrincipalAttributeId())) {
LOGGER.debug("No principal id attribute is found for LDAP authentication via [{}]", l.getLdapUrl());
} else {
handler.setPrincipalIdAttribute(l.getPrincipalAttributeId());
LOGGER.debug("Using principal id attribute [{}] for LDAP authentication via [{}]", l.getPrincipalAttributeId(), l.getLdapUrl());
}
final LdapPasswordPolicyProperties passwordPolicy = l.getPasswordPolicy();
if (passwordPolicy.isEnabled()) {
LOGGER.debug("Password policy is enabled for [{}]. Constructing password policy configuration", l.getLdapUrl());
final PasswordPolicyConfiguration cfg = createLdapPasswordPolicyConfiguration(passwordPolicy, authenticator, multiMapAttributes);
handler.setPasswordPolicyConfiguration(cfg);
}
final Map<String, Object> attributes = CollectionUtils.wrap(multiMapAttributes);
handler.setPrincipalAttributeMap(attributes);
LOGGER.debug("Initializing LDAP authentication handler for [{}]", l.getLdapUrl());
handler.initialize();
handlers.add(handler);
});
return handlers;
}
private static Predicate<LdapAuthenticationProperties> ldapInstanceConfigurationPredicate() {
return l -> {
if (l.getType() == null) {
LOGGER.warn("Skipping LDAP authentication entry since no type is defined");
return false;
}
if (StringUtils.isBlank(l.getLdapUrl())) {
LOGGER.warn("Skipping LDAP authentication entry since no LDAP url is defined");
return false;
}
return true;
};
}
private AuthenticationPasswordPolicyHandlingStrategy<AuthenticationResponse, PasswordPolicyConfiguration>
createLdapPasswordPolicyHandlingStrategy(final LdapAuthenticationProperties l) {
if (l.getPasswordPolicy().getStrategy() == LdapPasswordPolicyProperties.PasswordPolicyHandlingOptions.REJECT_RESULT_CODE) {
LOGGER.debug("Created LDAP password policy handling strategy based on blacklisted authentication result codes");
return new RejectResultCodeLdapPasswordPolicyHandlingStrategy();
}
final Resource location = l.getPasswordPolicy().getGroovy().getLocation();
if (l.getPasswordPolicy().getStrategy() == LdapPasswordPolicyProperties.PasswordPolicyHandlingOptions.GROOVY && location != null) {
LOGGER.debug("Created LDAP password policy handling strategy based on Groovy script [{}]", location);
return new GroovyPasswordPolicyHandlingStrategy(location);
}
LOGGER.debug("Created default LDAP password policy handling strategy");
return new DefaultPasswordPolicyHandlingStrategy();
}
private PasswordPolicyConfiguration createLdapPasswordPolicyConfiguration(final LdapPasswordPolicyProperties passwordPolicy,
final Authenticator authenticator,
final Multimap<String, Object> attributes) {
final PasswordPolicyConfiguration cfg = new PasswordPolicyConfiguration(passwordPolicy);
final Set<AuthenticationResponseHandler> handlers = new HashSet<>();
final String customPolicyClass = passwordPolicy.getCustomPolicyClass();
if (StringUtils.isNotBlank(customPolicyClass)) {
try {
LOGGER.debug("Configuration indicates use of a custom password policy handler [{}]", customPolicyClass);
final Class<AuthenticationResponseHandler> clazz = (Class<AuthenticationResponseHandler>) Class.forName(customPolicyClass);
handlers.add(clazz.getDeclaredConstructor().newInstance());
} catch (final Exception e) {
LOGGER.warn("Unable to construct an instance of the password policy handler", e);
}
}
LOGGER.debug("Password policy authentication response handler is set to accommodate directory type: [{}]", passwordPolicy.getType());
switch (passwordPolicy.getType()) {
case AD:
handlers.add(new ActiveDirectoryAuthenticationResponseHandler(Period.ofDays(cfg.getPasswordWarningNumberOfDays())));
Arrays.stream(ActiveDirectoryAuthenticationResponseHandler.ATTRIBUTES).forEach(a -> {
LOGGER.debug("Configuring authentication to retrieve password policy attribute [{}]", a);
attributes.put(a, a);
});
break;
case FreeIPA:
Arrays.stream(FreeIPAAuthenticationResponseHandler.ATTRIBUTES).forEach(a -> {
LOGGER.debug("Configuring authentication to retrieve password policy attribute [{}]", a);
attributes.put(a, a);
});
handlers.add(new FreeIPAAuthenticationResponseHandler(
Period.ofDays(cfg.getPasswordWarningNumberOfDays()), cfg.getLoginFailures()));
break;
case EDirectory:
Arrays.stream(EDirectoryAuthenticationResponseHandler.ATTRIBUTES).forEach(a -> {
LOGGER.debug("Configuring authentication to retrieve password policy attribute [{}]", a);
attributes.put(a, a);
});
handlers.add(new EDirectoryAuthenticationResponseHandler(Period.ofDays(cfg.getPasswordWarningNumberOfDays())));
break;
default:
handlers.add(new PasswordPolicyAuthenticationResponseHandler());
handlers.add(new PasswordExpirationAuthenticationResponseHandler());
break;
}
authenticator.setAuthenticationResponseHandlers((AuthenticationResponseHandler[]) handlers.toArray(new AuthenticationResponseHandler[0]));
LOGGER.debug("LDAP authentication response handlers configured are: [{}]", handlers);
if (!passwordPolicy.isAccountStateHandlingEnabled()) {
cfg.setAccountStateHandler((response, configuration) -> new ArrayList<>(0));
LOGGER.debug("Handling LDAP account states is disabled via CAS configuration");
} else if (StringUtils.isNotBlank(passwordPolicy.getWarningAttributeName()) && StringUtils.isNotBlank(passwordPolicy.getWarningAttributeValue())) {
final OptionalWarningLdapAccountStateHandler accountHandler = new OptionalWarningLdapAccountStateHandler();
accountHandler.setDisplayWarningOnMatch(passwordPolicy.isDisplayWarningOnMatch());
accountHandler.setWarnAttributeName(passwordPolicy.getWarningAttributeName());
accountHandler.setWarningAttributeValue(passwordPolicy.getWarningAttributeValue());
accountHandler.setAttributesToErrorMap(passwordPolicy.getPolicyAttributes());
cfg.setAccountStateHandler(accountHandler);
LOGGER.debug("Configuring an warning account state handler for LDAP authentication for warning attribute [{}] and value [{}]",
passwordPolicy.getWarningAttributeName(), passwordPolicy.getWarningAttributeValue());
} else {
final DefaultLdapAccountStateHandler accountHandler = new DefaultLdapAccountStateHandler();
accountHandler.setAttributesToErrorMap(passwordPolicy.getPolicyAttributes());
cfg.setAccountStateHandler(accountHandler);
LOGGER.debug("Configuring the default account state handler for LDAP authentication");
}
return cfg;
}
@ConditionalOnMissingBean(name = "ldapAuthenticationEventExecutionPlanConfigurer")
@Bean
public AuthenticationEventExecutionPlanConfigurer ldapAuthenticationEventExecutionPlanConfigurer() {
return plan -> ldapAuthenticationHandlers().forEach(handler -> {
LOGGER.info("Registering LDAP authentication for [{}]", handler.getName());
plan.registerAuthenticationHandlerWithPrincipalResolver(handler, personDirectoryPrincipalResolver);
});
}
}
package com.test.cas.config.handlers;
import java.security.GeneralSecurityException;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.AuthenticationPasswordPolicyHandlingStrategy;
import org.apereo.cas.authentication.LdapAuthenticationHandler;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.ldaptive.auth.Authenticator;
public class CustomAuthHandler extends LdapAuthenticationHandler {
// private static Logger logger = LoggerFac
public CustomAuthHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory,
Integer order, Authenticator authenticator, AuthenticationPasswordPolicyHandlingStrategy strategy) {
super(name, servicesManager, principalFactory, order, authenticator, strategy);
}
/*public CustomLDAPAuthHandler() {
super(name, servicesManager, principalFactory, order, authenticator, strategy);
}*/
@Override
protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(UsernamePasswordCredential upc,
String originalPassword) throws GeneralSecurityException, PreventedException {
System.out.println("CustomLDAPAuthHandler - authenticateUsernamePasswordInternal");
return super.authenticateUsernamePasswordInternal(upc, originalPassword);
/*
if(isSessionAuthenticatedViaTS(upc.getUsername()) ) {
LDAPHelper dataPopulater = getDataPopulater();
LinkedHashMap<String, Object> employeeDetailsByEmail = null;
//loginLogger.info("CAS LOGIN - MODE: passwordless - USERNAME: "+upc.getUsername()+" - DEVICE: "+agentDevice+" - OS: "+agentName+" - BROWSER: "+bwrName+" "+bwrVersion);
try {
employeeDetailsByEmail = dataPopulater.getEmployeeDetailsByEmail(upc.getUsername());
} catch (NamingException e) {
log.error(" Exception while fetching employee data from LDAP", e);
throw new FailedLoginException("Invalid credentials.");
}
return customHandleResult(upc, employeeDetailsByEmail);
} else {
loginLogger.info("CAS LOGIN - MODE: password - USERNAME: "+upc.getUsername()+" - DEVICE: "+agentDevice+" - OS: "+agentName+" - BROWSER: "+bwrName+" "+bwrVersion);
try {
return super.authenticateUsernamePasswordInternal(upc);
}
catch(Exception e) {
throw new FailedLoginException("Uh oh! Invalid credentials");
}
}*/
}
/*private HandlerResult customHandleResult(final Credential credential, LinkedHashMap map) {
SimplePrincipal principal;
if(null != map) {
principal = new SimplePrincipal(credential.getId(), map);
} else {
principal = new SimplePrincipal(credential.getId(), new LinkedHashMap<String, Object>());
}
ArrayList messages = new ArrayList();
HandlerResult result = new HandlerResult(this, new BasicCredentialMetaData(credential), principal, messages);
return result;
}*/
/*public LDAPHelper getDataPopulater() {
if(dataPopulater == null) {
this.dataPopulater = new LDAPHelper();
}
return dataPopulater;
}
public void setDataPopulater(LDAPHelper dataPopulater) {
this.dataPopulater = dataPopulater;
}*/
/* private boolean isSessionAuthenticatedViaTS(String userName) {
log.info("isSessionAuthenticatedViaTS - starts");
try {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
UserAgent userAgent = UserAgent.parseUserAgentString(attr.getRequest().getHeader("User-Agent"));
OperatingSystem agent = userAgent.getOperatingSystem();
Browser bwr = userAgent.getBrowser();
agentName = agent.getName();
agentDevice = agent.getDeviceType().getName();
bwrName = bwr.getName();
bwrVersion = userAgent.getBrowserVersion().getMajorVersion();
if (attr != null && userName != null) {
HttpSession session = attr.getRequest().getSession(false);
Object tsAuth = session.getAttribute(ThumbsigninApi.TS_SESSSION_KEY);
Object tsUser = session.getAttribute(ThumbsigninApi.TS_USERID);
log.info("isSessionAuthenticatedViaTS - tsUser :"+tsUser);
if ((tsAuth instanceof Boolean) && ((Boolean)tsAuth) && (tsUser != null && (userName.equals(tsUser.toString())))) {
return true;
}
}
}
catch(Exception e) {
log.error("isSessionAuthenticatedViaTS - Exception",e);
}
log.info("isSessionAuthenticatedViaTS - ends");
return false;
}*/
}
pom.xml
Description: XML document
