/connector/src/main/java/org/jboss/as/connector/security/ElytronCallbackHandler.java

https://github.com/ochaloup/jboss-as · Java · 285 lines · 192 code · 27 blank · 66 comment · 63 complexity · c1a0035941730bff1ba721c952a3c84f MD5 · raw file

  1. /*
  2. * Copyright 2017 Red Hat, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.jboss.as.connector.security;
  17. import static org.jboss.as.connector.logging.ConnectorLogger.SUBSYSTEM_RA_LOGGER;
  18. import java.io.IOException;
  19. import java.io.Serializable;
  20. import java.security.AccessController;
  21. import java.security.Principal;
  22. import java.security.PrivilegedAction;
  23. import java.util.Arrays;
  24. import java.util.HashSet;
  25. import java.util.Set;
  26. import javax.resource.spi.security.PasswordCredential;
  27. import javax.security.auth.Subject;
  28. import javax.security.auth.callback.CallbackHandler;
  29. import javax.security.auth.callback.UnsupportedCallbackException;
  30. import javax.security.auth.message.callback.CallerPrincipalCallback;
  31. import javax.security.auth.message.callback.GroupPrincipalCallback;
  32. import javax.security.auth.message.callback.PasswordValidationCallback;
  33. import org.jboss.jca.core.spi.security.Callback;
  34. import org.wildfly.security.auth.principal.NamePrincipal;
  35. import org.wildfly.security.auth.server.RealmUnavailableException;
  36. import org.wildfly.security.auth.server.SecurityDomain;
  37. import org.wildfly.security.auth.server.SecurityIdentity;
  38. import org.wildfly.security.auth.server.ServerAuthenticationContext;
  39. import org.wildfly.security.authz.RoleMapper;
  40. import org.wildfly.security.authz.Roles;
  41. import org.wildfly.security.evidence.PasswordGuessEvidence;
  42. import org.wildfly.security.manager.WildFlySecurityManager;
  43. /**
  44. * An Elytron based {@link CallbackHandler} implementation designed for the Jakarta Connectors security inflow. It uses the information
  45. * obtained from the {@link javax.security.auth.callback.Callback}s to authenticate and authorize the identity supplied
  46. * by the resource adapter and inserts the {@link SecurityIdentity} representing the authorized identity in the subject's
  47. * private credentials set.
  48. *
  49. * @author Flavia Rainone
  50. * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
  51. */
  52. public class ElytronCallbackHandler implements CallbackHandler, Serializable {
  53. private final SecurityDomain securityDomain;
  54. private final Callback mappings;
  55. private Subject executionSubject;
  56. /**
  57. * Constructor
  58. * @param securityDomain the Elytron security domain used to establish the caller principal.
  59. * @param mappings The mappings.
  60. */
  61. public ElytronCallbackHandler(final SecurityDomain securityDomain, final Callback mappings) {
  62. this.securityDomain = securityDomain;
  63. this.mappings = mappings;
  64. }
  65. /**
  66. * {@inheritDoc}
  67. */
  68. public void handle(javax.security.auth.callback.Callback[] callbacks) throws UnsupportedCallbackException, IOException {
  69. if (SUBSYSTEM_RA_LOGGER.isTraceEnabled())
  70. SUBSYSTEM_RA_LOGGER.elytronHandlerHandle(Arrays.toString(callbacks));
  71. // work wrapper calls the callback handler a second time with default callback values after the handler was invoked
  72. // by the RA. We must check if the execution subject already contains an identity and allow for replacement of the
  73. // identity with values found in the default callbacks only if the subject has no identity yet or if the identity
  74. // is the anonymous one.
  75. if (this.executionSubject != null) {
  76. final SecurityIdentity subjectIdentity = this.getPrivateCredential(this.executionSubject, SecurityIdentity.class);
  77. if (subjectIdentity != null && !subjectIdentity.isAnonymous()) {
  78. return;
  79. }
  80. }
  81. if (callbacks != null && callbacks.length > 0)
  82. {
  83. if (this.mappings != null && this.mappings.isMappingRequired())
  84. {
  85. callbacks = this.mappings.mapCallbacks(callbacks);
  86. }
  87. GroupPrincipalCallback groupPrincipalCallback = null;
  88. CallerPrincipalCallback callerPrincipalCallback = null;
  89. PasswordValidationCallback passwordValidationCallback = null;
  90. for (javax.security.auth.callback.Callback callback : callbacks) {
  91. if (callback instanceof GroupPrincipalCallback) {
  92. groupPrincipalCallback = (GroupPrincipalCallback) callback;
  93. if (this.executionSubject == null) {
  94. this.executionSubject = groupPrincipalCallback.getSubject();
  95. } else if (!this.executionSubject.equals(groupPrincipalCallback.getSubject())) {
  96. // TODO merge the contents of the subjects?
  97. }
  98. } else if (callback instanceof CallerPrincipalCallback) {
  99. callerPrincipalCallback = (CallerPrincipalCallback) callback;
  100. if (this.executionSubject == null) {
  101. this.executionSubject = callerPrincipalCallback.getSubject();
  102. } else if (!this.executionSubject.equals(callerPrincipalCallback.getSubject())) {
  103. // TODO merge the contents of the subjects?
  104. }
  105. } else if (callback instanceof PasswordValidationCallback) {
  106. passwordValidationCallback = (PasswordValidationCallback) callback;
  107. if (this.executionSubject == null) {
  108. this.executionSubject = passwordValidationCallback.getSubject();
  109. } else if (!this.executionSubject.equals(passwordValidationCallback.getSubject())) {
  110. // TODO merge the contents of the subjects?
  111. }
  112. } else {
  113. throw new UnsupportedCallbackException(callback);
  114. }
  115. }
  116. this.handleInternal(callerPrincipalCallback, groupPrincipalCallback, passwordValidationCallback);
  117. }
  118. }
  119. protected void handleInternal(final CallerPrincipalCallback callerPrincipalCallback, final GroupPrincipalCallback groupPrincipalCallback,
  120. final PasswordValidationCallback passwordValidationCallback) throws IOException {
  121. if(this.executionSubject == null) {
  122. throw SUBSYSTEM_RA_LOGGER.executionSubjectNotSetInHandler();
  123. }
  124. SecurityIdentity identity = this.securityDomain.getAnonymousSecurityIdentity();
  125. // establish the caller principal using the info from the callback.
  126. Principal callerPrincipal = null;
  127. if (callerPrincipalCallback != null) {
  128. Principal callbackPrincipal = callerPrincipalCallback.getPrincipal();
  129. callerPrincipal = callbackPrincipal != null ? new NamePrincipal(callbackPrincipal.getName()) :
  130. callerPrincipalCallback.getName() != null ? new NamePrincipal(callerPrincipalCallback.getName()) : null;
  131. }
  132. // a null principal is the ra contract for requiring the use of the unauthenticated identity - no point in attempting to authenticate.
  133. if (callerPrincipal != null) {
  134. // check if we have a username/password pair to authenticate - first try the password validation callback.
  135. if (passwordValidationCallback != null) {
  136. final String username = passwordValidationCallback.getUsername();
  137. final char[] password = passwordValidationCallback.getPassword();
  138. try {
  139. identity = this.authenticate(username, password);
  140. // add a password credential to the execution subject and set the successful result in the callback.
  141. this.addPrivateCredential(this.executionSubject, new PasswordCredential(username, password));
  142. passwordValidationCallback.setResult(true);
  143. } catch (SecurityException e) {
  144. passwordValidationCallback.setResult(false);
  145. return;
  146. }
  147. } else {
  148. // identity not established using the callback - check if the execution subject contains a password credential.
  149. PasswordCredential passwordCredential = this.getPrivateCredential(this.executionSubject, PasswordCredential.class);
  150. if (passwordCredential != null) {
  151. try {
  152. identity = this.authenticate(passwordCredential.getUserName(), passwordCredential.getPassword());
  153. } catch (SecurityException e) {
  154. return;
  155. }
  156. } else {
  157. identity = securityDomain.createAdHocIdentity(callerPrincipal);
  158. }
  159. }
  160. // at this point we either have an authenticated identity or an anonymous one. We must now check if the caller principal
  161. // is different from the identity principal and switch to the caller principal identity if needed.
  162. if (!callerPrincipal.equals(identity.getPrincipal())) {
  163. identity = identity.createRunAsIdentity(callerPrincipal.getName());
  164. }
  165. // if we have new roles coming from the group callback, set a new mapper in the identity.
  166. if (groupPrincipalCallback != null) {
  167. String[] groups = groupPrincipalCallback.getGroups();
  168. if (groups != null) {
  169. Set<String> roles = new HashSet<>(Arrays.asList(groups));
  170. // TODO what category should we use here?
  171. identity = identity.withRoleMapper(ElytronSecurityIntegration.SECURITY_IDENTITY_ROLE, RoleMapper.constant(Roles.fromSet(roles)));
  172. }
  173. }
  174. }
  175. // set the authenticated identity as a private credential in the subject.
  176. this.executionSubject.getPrincipals().add(identity.getPrincipal());
  177. this.addPrivateCredential(executionSubject, identity);
  178. }
  179. /**
  180. * Authenticate the user with the given credential against the configured Elytron security domain.
  181. *
  182. * @param username the user being authenticated.
  183. * @param credential the credential used as evidence to verify the user's identity.
  184. * @return the authenticated and authorized {@link SecurityIdentity}.
  185. * @throws IOException if an error occurs while authenticating the user.
  186. */
  187. private SecurityIdentity authenticate(final String username, final char[] credential) throws IOException {
  188. final ServerAuthenticationContext context = this.securityDomain.createNewAuthenticationContext();
  189. final PasswordGuessEvidence evidence = new PasswordGuessEvidence(credential != null ? credential : null);
  190. try {
  191. context.setAuthenticationName(username);
  192. if (context.verifyEvidence(evidence)) {
  193. if (context.authorize()) {
  194. context.succeed();
  195. return context.getAuthorizedIdentity();
  196. } else {
  197. context.fail();
  198. throw new SecurityException("Authorization failed");
  199. }
  200. } else {
  201. context.fail();
  202. throw new SecurityException("Authentication failed");
  203. }
  204. } catch (IllegalArgumentException | IllegalStateException | RealmUnavailableException e) {
  205. context.fail();
  206. throw e;
  207. } finally {
  208. if (!context.isDone()) {
  209. context.fail();
  210. }
  211. evidence.destroy();
  212. }
  213. }
  214. protected<T> T getPrivateCredential(final Subject subject, final Class<T> credentialClass) {
  215. T credential = null;
  216. if (subject != null) {
  217. Set<T> credentialSet;
  218. if (!WildFlySecurityManager.isChecking()) {
  219. credentialSet = subject.getPrivateCredentials(credentialClass);
  220. } else {
  221. credentialSet = AccessController.doPrivileged((PrivilegedAction<Set<T>>) () ->
  222. subject.getPrivateCredentials(credentialClass));
  223. }
  224. if (!credentialSet.isEmpty()) {
  225. credential = credentialSet.iterator().next();
  226. }
  227. }
  228. return credential;
  229. }
  230. /**
  231. * Add the specified credential to the subject's private credentials set.
  232. *
  233. * @param subject the {@link Subject} to add the credential to.
  234. * @param credential a reference to the credential.
  235. */
  236. protected void addPrivateCredential(final Subject subject, final Object credential) {
  237. if (!WildFlySecurityManager.isChecking()) {
  238. subject.getPrivateCredentials().add(credential);
  239. }
  240. else {
  241. AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
  242. subject.getPrivateCredentials().add(credential);
  243. return null;
  244. });
  245. }
  246. }
  247. /**
  248. * {@inheritDoc}
  249. */
  250. @Override
  251. public String toString() {
  252. StringBuilder sb = new StringBuilder();
  253. sb.append("ElytronCallbackHandler@").append(Integer.toHexString(System.identityHashCode(this)));
  254. sb.append("[mappings=").append(mappings);
  255. sb.append("]");
  256. return sb.toString();
  257. }
  258. }