View Javadoc
1   /*
2    * Copyright 2001-2008 The Apache Software Foundation.
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   */
17  package org.apache.juddi.api.impl;
18  
19  import java.util.Date;
20  import javax.annotation.Resource;
21  
22  import javax.persistence.EntityManager;
23  import javax.servlet.http.HttpServletRequest;
24  import javax.xml.datatype.DatatypeConfigurationException;
25  import javax.xml.datatype.DatatypeFactory;
26  import javax.xml.ws.WebServiceContext;
27  import javax.xml.ws.handler.MessageContext;
28  
29  import org.apache.commons.configuration.ConfigurationException;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.juddi.config.AppConfig;
33  import org.apache.juddi.config.Property;
34  import org.apache.juddi.model.UddiEntityPublisher;
35  import org.apache.juddi.v3.auth.Authenticator;
36  import org.apache.juddi.v3.auth.AuthenticatorFactory;
37  import org.apache.juddi.v3.error.AuthTokenRequiredException;
38  import org.apache.juddi.v3.error.AuthTokenExpiredException;
39  import org.apache.juddi.v3.error.ErrorMessage;
40  import org.uddi.v3_service.DispositionReportFaultMessage;
41  
42  /**
43   * Although this class is abstract, it provides token validation
44   *
45   * @author <a href="mailto:jfaath@apache.org">Jeff Faath</a>
46   *
47   * @author <a href="mailto:alexoree@apache.org">Alex O'Ree</a> - modified to
48   * include token expiration validation
49   */
50  public abstract class AuthenticatedService {
51  
52          /**
53           * @return the node
54           */
55          public String getNode() {
56              return node;
57          }
58  
59          public static final String UTF8 = "UTF-8";
60          public static final int AUTHTOKEN_ACTIVE = 1;
61          public static final int AUTHTOKEN_RETIRED = 0;
62          static final Log logger = LogFactory.getLog(AuthenticatedService.class);
63          /**
64           * the node id of this server instance, as loaded from the config file
65           */
66          private String node = "UNDEFINED_NODE_NAME";
67          protected String baseUrlSSL = "UNDEFINED";
68          protected String baseUrl = "UNDEFINED";
69          protected DatatypeFactory df = null;
70  
71          public AuthenticatedService() {
72                  try {
73                          node = AppConfig.getConfiguration().getString(Property.JUDDI_NODE_ID, "UNDEFINED_NODE_NAME");
74                          node = node.trim();
75                          baseUrlSSL = AppConfig.getConfiguration().getString(Property.JUDDI_BASE_URL_SECURE, Property.DEFAULT_BASE_URL_SECURE);
76                          baseUrlSSL = baseUrlSSL.trim();
77                  } catch (ConfigurationException ex) {
78                          logger.fatal(null, ex);
79                  }
80                  init();
81          }
82          
83          /**
84           * this method can be used to explicitly set a request context. this is useful
85           * in unit tests, embedded and in-vm scenarios only
86           * @param ctx 
87           * @since 3.3.8
88           */
89          public void setContext(WebServiceContext ctx) {
90                  this.ctx = ctx;
91          }
92          
93          private synchronized  void init() {
94              try {
95                  df = DatatypeFactory.newInstance();
96              } catch (DatatypeConfigurationException ex) {
97                  logger.fatal(null, ex);
98              }
99          }
100 
101         @Resource
102         protected WebServiceContext ctx;
103 
104         public UddiEntityPublisher getEntityPublisher(EntityManager em, String authInfo) throws DispositionReportFaultMessage {
105                 boolean useAuthInfo = true;
106                 try {
107                         useAuthInfo = AppConfig.getConfiguration().getBoolean(Property.JUDDI_AUTHENTICATOR_USE_TOKEN, true);
108                 } catch (ConfigurationException ex) {
109 
110                 }
111                 if (useAuthInfo) {
112 
113                         if (authInfo == null || authInfo.length() == 0) {
114                                 throw new AuthTokenRequiredException(new ErrorMessage("errors.auth.AuthRequired"));
115                         }
116 
117                         org.apache.juddi.model.AuthToken modelAuthToken = em.find(org.apache.juddi.model.AuthToken.class, authInfo);
118                         if (modelAuthToken == null) {
119                                 throw new AuthTokenRequiredException(new ErrorMessage("errors.auth.AuthInvalid"));
120                         }
121 
122                         int allowedMinutesOfInactivity = 0;
123                         try {
124                                 allowedMinutesOfInactivity = AppConfig.getConfiguration().getInt(Property.JUDDI_AUTH_TOKEN_TIMEOUT, 0);
125                         } catch (ConfigurationException ce) {
126                                 logger.error("Error reading property " + Property.JUDDI_AUTH_TOKEN_EXPIRATION + " from "
127                                         + "the application's configuration. No automatic timeout token invalidation will occur. "
128                                         + ce.getMessage(), ce);
129                         }
130                         int maxMinutesOfAge = 0;
131                         try {
132                                 maxMinutesOfAge = AppConfig.getConfiguration().getInt(Property.JUDDI_AUTH_TOKEN_EXPIRATION, 0);
133                         } catch (ConfigurationException ce) {
134                                 logger.error("Error reading property " + Property.JUDDI_AUTH_TOKEN_EXPIRATION + " from "
135                                         + "the application's configuration. No automatic timeout token invalidation will occur. "
136                                         + ce.getMessage(), ce);
137                         }
138                         Date now = new Date();
139                         // 0 or negative means token does not expire
140                         if (allowedMinutesOfInactivity > 0) {
141                                 // expire tokens after # minutes of inactivity
142                                 // compare the time in milli-seconds
143                                 if (now.getTime() > modelAuthToken.getLastUsed().getTime() + allowedMinutesOfInactivity * 60000l) {
144                                         logger.info("AUDIT: FAILTURE Token " + modelAuthToken.getAuthToken() + " expired due to inactivity " + getRequestorsIPAddress());
145                                         modelAuthToken.setTokenState(AUTHTOKEN_RETIRED);
146                                 }
147                         }
148                         if (maxMinutesOfAge > 0) {
149                                 // expire tokens when max age is reached
150                                 // compare the time in milli-seconds
151                                 if (now.getTime() > modelAuthToken.getCreated().getTime() + maxMinutesOfAge * 60000l) {
152 
153                                         logger.info("AUDIT: FAILURE - Token " + modelAuthToken.getAuthorizedName() + " expired due to old age " + getRequestorsIPAddress());
154                                         modelAuthToken.setTokenState(AUTHTOKEN_RETIRED);
155                                 }
156                         }
157 
158                         if (modelAuthToken.getTokenState() == AUTHTOKEN_RETIRED) {
159 
160                                 throw new AuthTokenExpiredException(new ErrorMessage("errors.auth.AuthTokenExpired"));
161                         }
162                         if (ctx != null) {
163                                 try {
164                                         boolean check = true;
165                                         try {
166                                                 check = AppConfig.getConfiguration().getBoolean(Property.JUDDI_AUTH_TOKEN_ENFORCE_SAME_IP, true);
167                                         } catch (ConfigurationException ex) {
168                                                 logger.warn("Error loading config property " + Property.JUDDI_AUTH_TOKEN_ENFORCE_SAME_IP
169                                                         + " Enforcing Same IP for Auth Tokens will be enabled by default", ex);
170                                         }
171                                         if (check) {
172                                                 MessageContext mc = ctx.getMessageContext();
173                                                 HttpServletRequest req = null;
174                                                 if (mc != null) {
175                                                         req = (HttpServletRequest) mc.get(MessageContext.SERVLET_REQUEST);
176                                                 }
177                                                 if (req != null
178                                                         && req.getRemoteAddr() != null
179                                                         && modelAuthToken.getIPAddress() != null
180                                                         && !modelAuthToken.getIPAddress().equalsIgnoreCase(req.getRemoteAddr())) {
181                                                         modelAuthToken.setTokenState(AUTHTOKEN_RETIRED);
182                                                         logger.error("AUDIT FAILURE - Security Alert - Attempt to use issued auth token from a different IP address, user "
183                                                                 + modelAuthToken.getAuthorizedName() + ", issued IP " + modelAuthToken.getIPAddress()
184                                                                 + ", attempted use from " + req.getRemoteAddr() + ", forcing reauthentication.");
185                                                         throw new AuthTokenRequiredException(new ErrorMessage("errors.auth.AuthInvalid"));
186                                                         //invalidate the token, someone's intercepted it or it was reused on another ip
187                                                 }
188                                         }
189                                 } catch (Exception ex) {
190                                         if (ex instanceof AuthTokenRequiredException) {
191                                                 throw (AuthTokenRequiredException) ex;
192                                         }
193                                         logger.error("unexpected error caught looking up requestor's ip address", ex);
194                                 }
195 
196                         }
197                         Authenticator authenticator = AuthenticatorFactory.getAuthenticator();
198                         UddiEntityPublisher entityPublisher = authenticator.identify(authInfo, modelAuthToken.getAuthorizedName(), ctx);
199 
200                         // Must make sure the returned publisher has all the necessary fields filled
201                         if (entityPublisher == null) {
202                                 logger.warn("AUDIT FAILURE - Auth token invalid, publisher does not exist " + getRequestorsIPAddress());
203                                 throw new AuthTokenRequiredException(new ErrorMessage("errors.auth.AuthInvalid"));
204                         }
205                         if (entityPublisher.getAuthorizedName() == null) {
206                                 logger.warn("AUDIT FAILURE - Auth token invalid, username does exist" + getRequestorsIPAddress());
207                                 throw new AuthTokenRequiredException(new ErrorMessage("errors.auth.AuthInvalid"));
208                         }
209                         // Auth token is being used.  Adjust appropriate values so that it's internal 'expiration clock' is reset.
210                         modelAuthToken.setLastUsed(new Date());
211                         modelAuthToken.setNumberOfUses(modelAuthToken.getNumberOfUses() + 1);
212                         return entityPublisher;
213                 } else {
214                         //use non-token based authentication
215                         Authenticator authenticator = AuthenticatorFactory.getAuthenticator();
216                         UddiEntityPublisher entityPublisher = authenticator.identify(null, null, ctx);
217                         // Must make sure the returned publisher has all the necessary fields filled
218                         if (entityPublisher == null) {
219                                 logger.warn("AUDIT FAILURE - Auth token invalid, publisher does not exist " + getRequestorsIPAddress());
220                                 throw new AuthTokenRequiredException(new ErrorMessage("errors.auth.AuthInvalid"));
221                         }
222                         if (entityPublisher.getAuthorizedName() == null) {
223                                 logger.warn("AUDIT FAILURE - Auth token invalid, username does exist" + getRequestorsIPAddress());
224                                 throw new AuthTokenRequiredException(new ErrorMessage("errors.auth.AuthInvalid"));
225                         }
226                         return entityPublisher;
227 
228                 }
229 
230         }
231 
232         /**
233          * Attempts to get the requestor's ip address from the servlet context,
234          * defaults to null it it can't be retrieved
235          *
236          * @return requestor's ip address or null if it's not available
237          */
238         public String getRequestorsIPAddress() {
239                 try {
240                         MessageContext mc = ctx.getMessageContext();
241                         HttpServletRequest req = null;
242                         if (mc != null) {
243                                 req = (HttpServletRequest) mc.get(MessageContext.SERVLET_REQUEST);
244                         }
245                         if (req != null) {
246                                 return req.getRemoteAddr();
247                         }
248                 } catch (Exception ex) {
249                         logger.debug("Error caught looking up the requestor's ip address", ex);
250                 }
251                 return null;
252         }
253 }