PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/support/cas-server-support-saml-core-api/src/main/java/org/apereo/cas/support/saml/util/AbstractSaml20ObjectBuilder.java

https://github.com/frett/cas
Java | 492 lines | 290 code | 51 blank | 151 comment | 40 complexity | e9227cdad3a0e04573e2f077353720db MD5 | raw file
  1. package org.apereo.cas.support.saml.util;
  2. import org.apereo.cas.authentication.principal.WebApplicationService;
  3. import org.apereo.cas.support.saml.OpenSamlConfigBean;
  4. import org.apereo.cas.util.CompressionUtils;
  5. import org.apereo.cas.util.DateTimeUtils;
  6. import org.apereo.cas.util.EncodingUtils;
  7. import org.apereo.cas.util.InetAddressUtils;
  8. import org.apereo.cas.util.RandomUtils;
  9. import lombok.extern.slf4j.Slf4j;
  10. import lombok.val;
  11. import org.apache.commons.lang3.StringUtils;
  12. import org.joda.time.DateTime;
  13. import org.opensaml.core.xml.XMLObject;
  14. import org.opensaml.saml.common.SAMLVersion;
  15. import org.opensaml.saml.saml2.core.Assertion;
  16. import org.opensaml.saml.saml2.core.Attribute;
  17. import org.opensaml.saml.saml2.core.AttributeStatement;
  18. import org.opensaml.saml.saml2.core.AttributeValue;
  19. import org.opensaml.saml.saml2.core.Audience;
  20. import org.opensaml.saml.saml2.core.AudienceRestriction;
  21. import org.opensaml.saml.saml2.core.AuthnContext;
  22. import org.opensaml.saml.saml2.core.AuthnContextClassRef;
  23. import org.opensaml.saml.saml2.core.AuthnStatement;
  24. import org.opensaml.saml.saml2.core.Conditions;
  25. import org.opensaml.saml.saml2.core.Issuer;
  26. import org.opensaml.saml.saml2.core.LogoutRequest;
  27. import org.opensaml.saml.saml2.core.NameID;
  28. import org.opensaml.saml.saml2.core.Response;
  29. import org.opensaml.saml.saml2.core.SessionIndex;
  30. import org.opensaml.saml.saml2.core.Statement;
  31. import org.opensaml.saml.saml2.core.Status;
  32. import org.opensaml.saml.saml2.core.StatusCode;
  33. import org.opensaml.saml.saml2.core.StatusMessage;
  34. import org.opensaml.saml.saml2.core.Subject;
  35. import org.opensaml.saml.saml2.core.SubjectConfirmation;
  36. import org.opensaml.saml.saml2.core.SubjectConfirmationData;
  37. import org.opensaml.soap.soap11.ActorBearing;
  38. import java.nio.charset.StandardCharsets;
  39. import java.time.ZonedDateTime;
  40. import java.util.ArrayList;
  41. import java.util.Arrays;
  42. import java.util.Collection;
  43. import java.util.List;
  44. import java.util.Map;
  45. /**
  46. * This is {@link AbstractSaml20ObjectBuilder}.
  47. * to build saml2 objects.
  48. *
  49. * @author Misagh Moayyed
  50. * @since 4.1
  51. */
  52. @Slf4j
  53. public abstract class AbstractSaml20ObjectBuilder extends AbstractSamlObjectBuilder {
  54. private static final long serialVersionUID = -4325127376598205277L;
  55. public AbstractSaml20ObjectBuilder(final OpenSamlConfigBean configBean) {
  56. super(configBean);
  57. }
  58. private static void configureAttributeNameFormat(final Attribute attribute, final String nameFormat) {
  59. if (StringUtils.isBlank(nameFormat)) {
  60. return;
  61. }
  62. val compareFormat = nameFormat.trim().toLowerCase();
  63. if ("basic".equals(compareFormat) || compareFormat.equals(Attribute.BASIC)) {
  64. attribute.setNameFormat(Attribute.BASIC);
  65. } else if ("uri".equals(compareFormat) || compareFormat.equals(Attribute.URI_REFERENCE)) {
  66. attribute.setNameFormat(Attribute.URI_REFERENCE);
  67. } else if ("unspecified".equals(compareFormat) || compareFormat.equals(Attribute.UNSPECIFIED)) {
  68. attribute.setNameFormat(Attribute.UNSPECIFIED);
  69. } else {
  70. attribute.setNameFormat(nameFormat);
  71. }
  72. }
  73. /**
  74. * Gets name id.
  75. *
  76. * @param nameIdFormat the name id format
  77. * @param nameIdValue the name id value
  78. * @return the name iD
  79. */
  80. public NameID getNameID(final String nameIdFormat, final String nameIdValue) {
  81. val nameId = newSamlObject(NameID.class);
  82. nameId.setFormat(nameIdFormat);
  83. nameId.setValue(nameIdValue);
  84. return nameId;
  85. }
  86. /**
  87. * Create a new SAML ECP response object.
  88. *
  89. * @param assertionConsumerUrl the assertion consumer url
  90. * @return the response
  91. */
  92. public org.opensaml.saml.saml2.ecp.Response newEcpResponse(final String assertionConsumerUrl) {
  93. val samlResponse = newSamlObject(org.opensaml.saml.saml2.ecp.Response.class);
  94. samlResponse.setSOAP11MustUnderstand(Boolean.TRUE);
  95. samlResponse.setSOAP11Actor(ActorBearing.SOAP11_ACTOR_NEXT);
  96. samlResponse.setAssertionConsumerServiceURL(assertionConsumerUrl);
  97. return samlResponse;
  98. }
  99. /**
  100. * Create a new SAML response object.
  101. *
  102. * @param id the id
  103. * @param issueInstant the issue instant
  104. * @param recipient the recipient
  105. * @param service the service
  106. * @return the response
  107. */
  108. public Response newResponse(final String id, final ZonedDateTime issueInstant,
  109. final String recipient, final WebApplicationService service) {
  110. val samlResponse = newSamlObject(Response.class);
  111. samlResponse.setID(id);
  112. samlResponse.setIssueInstant(DateTimeUtils.dateTimeOf(issueInstant));
  113. samlResponse.setVersion(SAMLVersion.VERSION_20);
  114. if (StringUtils.isNotBlank(recipient)) {
  115. LOGGER.debug("Setting provided RequestId {} as InResponseTo", recipient);
  116. samlResponse.setInResponseTo(recipient);
  117. } else {
  118. LOGGER.debug("No recipient is provided. Skipping InResponseTo");
  119. }
  120. return samlResponse;
  121. }
  122. /**
  123. * Create a new SAML status object.
  124. *
  125. * @param codeValue the code value
  126. * @param statusMessage the status message
  127. * @return the status
  128. */
  129. public Status newStatus(final String codeValue, final String statusMessage) {
  130. val status = newSamlObject(Status.class);
  131. val statusCode = newSamlObject(StatusCode.class);
  132. statusCode.setValue(codeValue);
  133. status.setStatusCode(statusCode);
  134. if (StringUtils.isNotBlank(statusMessage)) {
  135. val message = newSamlObject(StatusMessage.class);
  136. message.setMessage(statusMessage);
  137. status.setStatusMessage(message);
  138. }
  139. return status;
  140. }
  141. /**
  142. * Create a new SAML1 response object.
  143. *
  144. * @param authnStatement the authn statement
  145. * @param issuer the issuer
  146. * @param issuedAt the issued at
  147. * @param id the id
  148. * @return the assertion
  149. */
  150. public Assertion newAssertion(final AuthnStatement authnStatement, final String issuer,
  151. final ZonedDateTime issuedAt, final String id) {
  152. val list = new ArrayList<Statement>();
  153. list.add(authnStatement);
  154. return newAssertion(list, issuer, issuedAt, id);
  155. }
  156. /**
  157. * Create a new SAML1 response object.
  158. *
  159. * @param authnStatement the authn statement
  160. * @param issuer the issuer
  161. * @param issuedAt the issued at
  162. * @param id the id
  163. * @return the assertion
  164. */
  165. public Assertion newAssertion(final List<Statement> authnStatement, final String issuer,
  166. final ZonedDateTime issuedAt, final String id) {
  167. val assertion = newSamlObject(Assertion.class);
  168. assertion.setID(id);
  169. assertion.setIssueInstant(DateTimeUtils.dateTimeOf(issuedAt));
  170. assertion.setIssuer(newIssuer(issuer));
  171. assertion.getStatements().addAll(authnStatement);
  172. return assertion;
  173. }
  174. /**
  175. * New saml2 logout request.
  176. *
  177. * @param id the id
  178. * @param issueInstant the issue instant
  179. * @param destination the destination
  180. * @param issuer the issuer
  181. * @param sessionIndex the session index
  182. * @param nameId the name id
  183. * @return the logout request
  184. */
  185. public LogoutRequest newLogoutRequest(final String id, final DateTime issueInstant,
  186. final String destination, final Issuer issuer,
  187. final String sessionIndex, final NameID nameId) {
  188. val request = newSamlObject(LogoutRequest.class);
  189. request.setID(id);
  190. request.setVersion(SAMLVersion.VERSION_20);
  191. request.setIssueInstant(issueInstant);
  192. request.setIssuer(issuer);
  193. request.setDestination(destination);
  194. if (StringUtils.isNotBlank(sessionIndex)) {
  195. val sessionIdx = newSamlObject(SessionIndex.class);
  196. sessionIdx.setSessionIndex(sessionIndex);
  197. request.getSessionIndexes().add(sessionIdx);
  198. }
  199. if (nameId != null) {
  200. request.setNameID(nameId);
  201. }
  202. return request;
  203. }
  204. /**
  205. * New issuer.
  206. *
  207. * @param issuerValue the issuer
  208. * @return the issuer
  209. */
  210. public Issuer newIssuer(final String issuerValue) {
  211. val issuer = newSamlObject(Issuer.class);
  212. issuer.setValue(issuerValue);
  213. return issuer;
  214. }
  215. /**
  216. * New attribute statement.
  217. *
  218. * @param attributes the attributes
  219. * @param attributeFriendlyNames the attribute friendly names
  220. * @param configuredNameFormats the configured name formats
  221. * @param defaultNameFormat the default name format
  222. * @param builder the builder
  223. * @return the attribute statement
  224. */
  225. public AttributeStatement newAttributeStatement(final Map<String, Object> attributes,
  226. final Map<String, String> attributeFriendlyNames,
  227. final Map<String, String> configuredNameFormats,
  228. final String defaultNameFormat,
  229. final Saml20AttributeBuilder builder) {
  230. val attrStatement = newSamlObject(AttributeStatement.class);
  231. for (val e : attributes.entrySet()) {
  232. if (e.getValue() instanceof Collection<?> && ((Collection<?>) e.getValue()).isEmpty()) {
  233. LOGGER.info("Skipping attribute [{}] because it does not have any values.", e.getKey());
  234. continue;
  235. }
  236. val friendlyName = attributeFriendlyNames.getOrDefault(e.getKey(), null);
  237. val attribute = newAttribute(friendlyName, e.getKey(), e.getValue(), configuredNameFormats, defaultNameFormat);
  238. builder.build(attrStatement, attribute);
  239. }
  240. return attrStatement;
  241. }
  242. /**
  243. * New attribute statement attribute statement.
  244. *
  245. * @param attributes the attributes
  246. * @param attributeFriendlyNames the attribute friendly names
  247. * @param configuredNameFormats the configured name formats
  248. * @param defaultNameFormat the default name format
  249. * @return the attribute statement
  250. */
  251. public AttributeStatement newAttributeStatement(final Map<String, Object> attributes,
  252. final Map<String, String> attributeFriendlyNames,
  253. final Map<String, String> configuredNameFormats,
  254. final String defaultNameFormat) {
  255. return newAttributeStatement(attributes, attributeFriendlyNames,
  256. configuredNameFormats, defaultNameFormat, new DefaultSaml20AttributeBuilder());
  257. }
  258. /**
  259. * Add saml2 attribute values for attribute.
  260. *
  261. * @param attributeName the attribute name
  262. * @param attributeValue the attribute value
  263. * @param attributeList the attribute list
  264. */
  265. public void addAttributeValuesToSaml2Attribute(final String attributeName,
  266. final Object attributeValue,
  267. final List<XMLObject> attributeList) {
  268. addAttributeValuesToSamlAttribute(attributeName, attributeValue, attributeList, AttributeValue.DEFAULT_ELEMENT_NAME);
  269. }
  270. /**
  271. * New attribute.
  272. *
  273. * @param attributeFriendlyName the attribute friendly name
  274. * @param attributeName the attribute name
  275. * @param attributeValue the attribute value
  276. * @param configuredNameFormats the configured name formats. If an attribute is found in this collection, the linked name format will be used.
  277. * @param defaultNameFormat the default name format
  278. * @return the attribute
  279. */
  280. protected Attribute newAttribute(final String attributeFriendlyName,
  281. final String attributeName,
  282. final Object attributeValue,
  283. final Map<String, String> configuredNameFormats,
  284. final String defaultNameFormat) {
  285. val attribute = newSamlObject(Attribute.class);
  286. attribute.setName(attributeName);
  287. if (StringUtils.isNotBlank(attributeFriendlyName)) {
  288. attribute.setFriendlyName(attributeFriendlyName);
  289. } else {
  290. attribute.setFriendlyName(attributeName);
  291. }
  292. addAttributeValuesToSaml2Attribute(attributeName, attributeValue, attribute.getAttributeValues());
  293. if (!configuredNameFormats.isEmpty() && configuredNameFormats.containsKey(attribute.getName())) {
  294. val nameFormat = configuredNameFormats.get(attribute.getName());
  295. LOGGER.debug("Found name format [{}] for attribute [{}]", nameFormat, attribute.getName());
  296. configureAttributeNameFormat(attribute, nameFormat);
  297. LOGGER.debug("Attribute [{}] is assigned the name format of [{}]", attribute.getName(), attribute.getNameFormat());
  298. } else {
  299. LOGGER.debug("Skipped name format, as no name formats are defined or none is found for attribute [{}]", attribute.getName());
  300. configureAttributeNameFormat(attribute, defaultNameFormat);
  301. }
  302. LOGGER.debug("Attribute [{}] has [{}] value(s)", attribute.getName(), attribute.getAttributeValues().size());
  303. return attribute;
  304. }
  305. /**
  306. * New authn statement.
  307. *
  308. * @param contextClassRef the context class ref such as {@link AuthnContext#PASSWORD_AUTHN_CTX}
  309. * @param authnInstant the authn instant
  310. * @param sessionIndex the session index
  311. * @return the authn statement
  312. */
  313. public AuthnStatement newAuthnStatement(final String contextClassRef, final ZonedDateTime authnInstant,
  314. final String sessionIndex) {
  315. LOGGER.debug("Building authentication statement with context class ref [{}] @ [{}] with index [{}]",
  316. contextClassRef, authnInstant, sessionIndex);
  317. val stmt = newSamlObject(AuthnStatement.class);
  318. val ctx = newSamlObject(AuthnContext.class);
  319. val classRef = newSamlObject(AuthnContextClassRef.class);
  320. classRef.setAuthnContextClassRef(contextClassRef);
  321. ctx.setAuthnContextClassRef(classRef);
  322. stmt.setAuthnContext(ctx);
  323. stmt.setAuthnInstant(DateTimeUtils.dateTimeOf(authnInstant));
  324. stmt.setSessionIndex(sessionIndex);
  325. return stmt;
  326. }
  327. /**
  328. * New conditions element.
  329. *
  330. * @param notBefore the not before
  331. * @param notOnOrAfter the not on or after
  332. * @param audienceUri the service id
  333. * @return the conditions
  334. */
  335. public Conditions newConditions(final ZonedDateTime notBefore, final ZonedDateTime notOnOrAfter, final String... audienceUri) {
  336. LOGGER.debug("Building conditions for audience [{}] that enforce not-before [{}] and not-after [{}]", audienceUri, notBefore, notOnOrAfter);
  337. val conditions = newSamlObject(Conditions.class);
  338. conditions.setNotBefore(DateTimeUtils.dateTimeOf(notBefore));
  339. conditions.setNotOnOrAfter(DateTimeUtils.dateTimeOf(notOnOrAfter));
  340. val audienceRestriction = newSamlObject(AudienceRestriction.class);
  341. Arrays.stream(audienceUri).forEach(audienceEntry -> {
  342. val audience = newSamlObject(Audience.class);
  343. audience.setAudienceURI(audienceEntry);
  344. audienceRestriction.getAudiences().add(audience);
  345. });
  346. conditions.getAudienceRestrictions().add(audienceRestriction);
  347. return conditions;
  348. }
  349. /**
  350. * New subject subject.
  351. *
  352. * @param nameIdFormat the name id format
  353. * @param nameIdValue the name id value
  354. * @param recipient the recipient
  355. * @param notOnOrAfter the not on or after
  356. * @param inResponseTo the in response to
  357. * @param notBefore the not before
  358. * @return the subject
  359. */
  360. public Subject newSubject(final String nameIdFormat, final String nameIdValue,
  361. final String recipient, final ZonedDateTime notOnOrAfter,
  362. final String inResponseTo, final ZonedDateTime notBefore) {
  363. val nameID = getNameID(nameIdFormat, nameIdValue);
  364. return newSubject(nameID, null, recipient, notOnOrAfter, inResponseTo, notBefore);
  365. }
  366. /**
  367. * New subject element.
  368. *
  369. * @param nameId the nameId
  370. * @param subjectConfNameId the subject conf name id
  371. * @param recipient the recipient
  372. * @param notOnOrAfter the not on or after
  373. * @param inResponseTo the in response to
  374. * @param notBefore the not before
  375. * @return the subject
  376. */
  377. public Subject newSubject(final NameID nameId,
  378. final NameID subjectConfNameId,
  379. final String recipient,
  380. final ZonedDateTime notOnOrAfter,
  381. final String inResponseTo,
  382. final ZonedDateTime notBefore) {
  383. LOGGER.debug("Building subject for NameID [{}] and recipient [{}], in response to [{}]", nameId, recipient, inResponseTo);
  384. val confirmation = newSamlObject(SubjectConfirmation.class);
  385. confirmation.setMethod(SubjectConfirmation.METHOD_BEARER);
  386. val data = newSamlObject(SubjectConfirmationData.class);
  387. if (StringUtils.isNotBlank(recipient)) {
  388. data.setRecipient(recipient);
  389. }
  390. if (notOnOrAfter != null) {
  391. data.setNotOnOrAfter(DateTimeUtils.dateTimeOf(notOnOrAfter));
  392. }
  393. if (StringUtils.isNotBlank(inResponseTo)) {
  394. data.setInResponseTo(inResponseTo);
  395. val ip = InetAddressUtils.getByName(inResponseTo);
  396. if (ip != null) {
  397. data.setAddress(ip.getHostName());
  398. }
  399. }
  400. if (notBefore != null) {
  401. data.setNotBefore(DateTimeUtils.dateTimeOf(notBefore));
  402. }
  403. confirmation.setSubjectConfirmationData(data);
  404. val subject = newSamlObject(Subject.class);
  405. if (nameId != null) {
  406. subject.setNameID(nameId);
  407. if (subjectConfNameId != null) {
  408. confirmation.setNameID(subjectConfNameId);
  409. }
  410. subject.setEncryptedID(null);
  411. confirmation.setEncryptedID(null);
  412. }
  413. subject.getSubjectConfirmations().add(confirmation);
  414. LOGGER.debug("Built subject [{}]", subject);
  415. return subject;
  416. }
  417. @Override
  418. public String generateSecureRandomId() {
  419. return RandomUtils.generateSecureRandomId();
  420. }
  421. /**
  422. * Decode authn request xml.
  423. *
  424. * @param encodedRequestXmlString the encoded request xml string
  425. * @return the request
  426. */
  427. public String decodeSamlAuthnRequest(final String encodedRequestXmlString) {
  428. if (StringUtils.isEmpty(encodedRequestXmlString)) {
  429. return null;
  430. }
  431. val decodedBytes = EncodingUtils.decodeBase64(encodedRequestXmlString);
  432. if (decodedBytes == null) {
  433. return null;
  434. }
  435. val inflated = CompressionUtils.inflate(decodedBytes);
  436. if (!StringUtils.isEmpty(inflated)) {
  437. return inflated;
  438. }
  439. return new String(decodedBytes, StandardCharsets.UTF_8);
  440. }
  441. }