View Javadoc

1   /*
2    * Licensed to the University Corporation for Advanced Internet Development, 
3    * Inc. (UCAID) under one or more contributor license agreements.  See the 
4    * NOTICE file distributed with this work for additional information regarding
5    * copyright ownership. The UCAID licenses this file to You under the Apache 
6    * License, Version 2.0 (the "License"); you may not use this file except in 
7    * compliance with the License.  You may obtain a copy of the License at
8    *
9    *    http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.principalConnector;
19  
20  import java.sql.SQLException;
21  
22  import org.opensaml.saml1.core.NameIdentifier;
23  import org.opensaml.saml2.core.AuthnRequest;
24  import org.opensaml.saml2.core.NameID;
25  import org.opensaml.saml2.core.SubjectQuery;
26  import org.opensaml.xml.XMLObject;
27  import org.opensaml.xml.util.DatatypeHelper;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolutionException;
32  import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.ShibbolethResolutionContext;
33  import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.StoredIDDataConnector;
34  import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.StoredIDStore;
35  import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.StoredIDStore.PersistentIdEntry;
36  import edu.internet2.middleware.shibboleth.common.profile.provider.SAMLProfileRequestContext;
37  
38  /**
39   * A principal connector that resolved ID created by {@link StoredIDPrincipalConnector}s into principals.
40   */
41  public class StoredIDPrincipalConnector extends BasePrincipalConnector {
42  
43      /** Class logger. */
44      private final Logger log = LoggerFactory.getLogger(StoredIDPrincipalConnector.class);
45  
46      /** ID store that manages the stored IDs. */
47      private StoredIDStore pidStore;
48  
49      /** Whether an empty result set is an error. */
50      private boolean noResultIsError;
51  
52      /**
53       * Constructor.
54       * 
55       * @param idProducer data connector that produced the stored ID.
56       */
57      public StoredIDPrincipalConnector(StoredIDDataConnector idProducer) {
58          if (idProducer == null) {
59              throw new IllegalArgumentException("ID producing data connector may not be null");
60          }
61          pidStore = idProducer.getStoredIDStore();
62          noResultIsError = false;
63      }
64  
65      /**
66       * This returns whether this connector will throw an exception if no search results are found. The default is false.
67       * 
68       * @return <code>boolean</code>
69       */
70      public boolean isNoResultIsError() {
71          return noResultIsError;
72      }
73  
74      /**
75       * This sets whether this connector will throw an exception if no search results are found.
76       * 
77       * @param isError <code>boolean</code>
78       */
79      public void setNoResultIsError(boolean isError) {
80          noResultIsError = isError;
81      }
82  
83      /** {@inheritDoc} */
84      public String resolve(ShibbolethResolutionContext resolutionContext) throws AttributeResolutionException {
85          SAMLProfileRequestContext requestContext = resolutionContext.getAttributeRequestContext();
86  
87          String persistentId;
88          if (requestContext.getSubjectNameIdentifier() instanceof NameIdentifier) {
89              persistentId = ((NameIdentifier) requestContext.getSubjectNameIdentifier()).getNameIdentifier();
90          } else if (requestContext.getSubjectNameIdentifier() instanceof NameID) {
91              persistentId = ((NameID) requestContext.getSubjectNameIdentifier()).getValue();
92          } else {
93              throw new AttributeResolutionException("Subject name identifier is not of a supported type");
94          }
95  
96          try {
97              PersistentIdEntry pidEntry = pidStore.getActivePersistentIdEntry(persistentId);
98              if (pidEntry == null) {
99                  if (noResultIsError) {
100                     log.warn("PersistentId '{}' not found", persistentId);
101                     throw new AttributeResolutionException("No identifier found");
102                 }
103                 return null;
104             }
105 
106             if (!DatatypeHelper.safeEquals(pidEntry.getPeerEntityId(), getPeerEntityId(resolutionContext))) {
107                 log.warn(
108                         "Requester '{}' attempted to use identifier '{}' which was issued to the entity '{}'",
109                         new Object[] {requestContext.getInboundMessageIssuer(), pidEntry.getPersistentId(),
110                                 pidEntry.getPeerEntityId(),});
111                 if (noResultIsError) {
112                     throw new AttributeResolutionException("identifier mismatch");
113                 }
114                 return null;
115             }
116 
117             return pidEntry.getPrincipalName();
118         } catch (SQLException e) {
119             log.error("Error retrieving persistent ID from database", e);
120             throw new AttributeResolutionException("Error retrieving persistent ID from database", e);
121         }
122 
123     }
124 
125     /**
126      * Gets the entity ID used for the peer. If the inbound request is a SAML 2 authentication context and contains a
127      * NameIDPolicy than the SPNameQualifier is used if present, otherwise the inbound message issuer is used.
128      * 
129      * @param resolutionContext current attribute resolution context
130      * 
131      * @return the entity ID to use for the peer
132      */
133     protected String getPeerEntityId(ShibbolethResolutionContext resolutionContext) {
134         SAMLProfileRequestContext requestContext = resolutionContext.getAttributeRequestContext();
135         
136         String peerEntityId = null;
137 
138         log.debug("Determining if peer entity ID will be the SPNameQualifier from a SAML 2 authentication statement");
139         XMLObject inboundMessage = requestContext.getInboundSAMLMessage();
140         if (inboundMessage instanceof AuthnRequest) {
141             AuthnRequest authnRequest = (AuthnRequest) inboundMessage;
142             if (authnRequest.getNameIDPolicy() != null) {
143                 peerEntityId = DatatypeHelper.safeTrimOrNullString(authnRequest.getNameIDPolicy().getSPNameQualifier());
144                 if (peerEntityId == null) {
145                     log.debug("SAML 2 authentication request did not contain an SPNameQualifier within its NameIDPolicy");
146                 } else {
147                     log.debug("SAML 2 authentication request contained an SPNameQualifier, within its NameIDPolicy.  Using that as peer entity ID");
148                 }
149             } else {
150                 log.debug("SAML 2 authentication request did not contain a NameIDPolicy");
151             }
152         } else if (inboundMessage instanceof SubjectQuery) {
153             SubjectQuery query = (SubjectQuery) inboundMessage;
154             if (query.getSubject().getNameID().getSPNameQualifier() != null) {
155                 peerEntityId =
156                         DatatypeHelper.safeTrimOrNullString(query.getSubject().getNameID().getSPNameQualifier());
157                 if (peerEntityId == null) {
158                     log.debug("SAML 2 subject query did not contain an SPNameQualifier within its NameID");
159                 } else {
160                     log.debug("SAML 2 subject query contained an SPNameQualifier, within its NameID.  Using that as peer entity ID");
161                 }
162             } else {
163                 log.debug("SAML 2 attribute query did not contain a SPNameQualifier");
164             }
165         } else {
166             peerEntityId = requestContext.getInboundMessageIssuer(); 
167         }
168 
169         if (peerEntityId == null) {
170             log.debug("Determining if inbound message issuer is available for use as peer entity ID");
171             peerEntityId = resolutionContext.getAttributeRequestContext().getInboundMessageIssuer();
172         }
173 
174         return peerEntityId;
175     }
176 
177     /** {@inheritDoc} */
178     public void validate() throws AttributeResolutionException {
179         if (pidStore == null) {
180             throw new AttributeResolutionException("Persistent ID store was null");
181         }
182 
183         try {
184             pidStore.getPersistentIdEntry("test", false);
185         } catch (SQLException e) {
186             throw new AttributeResolutionException("Persistent ID store can not perform persistent ID search", e);
187         }
188     }
189 }