/*
* Created on Jun 25, 2005
*
* Copyright 2005 CafeSip.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.cafesip.jiplet.cma;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import javax.sip.RequestEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipProvider;
import javax.sip.address.SipURI;
import javax.sip.header.AuthorizationHeader;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ExpiresHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.ProxyAuthenticateHeader;
import javax.sip.header.ProxyAuthorizationHeader;
import javax.sip.header.WWWAuthenticateHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import org.cafesip.jiplet.Jiplet;
import org.cafesip.jiplet.JipletContainer;
import org.cafesip.jiplet.JipletContext;
import org.cafesip.jiplet.JipletException;
import org.cafesip.jiplet.JipletLogger;
import org.cafesip.jiplet.JipletPrincipal;
import org.cafesip.jiplet.Pair;
import org.cafesip.jiplet.Realm;
import org.cafesip.jiplet.config.jip.AuthConstraint;
import org.cafesip.jiplet.config.jip.JipApplication;
import org.cafesip.jiplet.config.jip.SecurityConstraint;
/**
* @author Amit Chatterjee
*
*/
public class SecurityConstraintManager extends TimerTask
{
private Authorizations authorizations = new Authorizations();
private JipletContext context;
private HashMap jipletMap = new HashMap();
// key= jiplet, value = Pair(realm, allowed roles)
private Timer timer = new Timer();
private static final long DEFAULT_CACHE_PERIOD = 30 * 60 * 1000L; // 30
// minutes
private long cachePeriod = DEFAULT_CACHE_PERIOD;
private boolean authOnLogout = true;
/**
* A constructor for this class.
*
*
*/
public SecurityConstraintManager(JipletContext context)
{
super();
this.context = context;
timer.scheduleAtFixedRate(this, 10000L, 10000L);
}
public void init(JipApplication config) throws Exception
{
if (JipletContainer.getInstance().getDefaultRealm() == null)
{
JipletLogger
.info("Realms have not been configured for this system. We will ignore any security constraints");
return;
}
Iterator iter = config.getSecurityConstraint().iterator();
while (iter.hasNext() == true)
{
SecurityConstraint constraint = (SecurityConstraint) iter.next();
AuthConstraint auth = constraint.getAuthConstraint();
Iterator i = auth.getRoleName().iterator();
String[] roles = new String[auth.getRoleName().size()];
int index = 0;
while (i.hasNext() == true)
{
String role = (String) i.next();
roles[index++] = role;
}
i = constraint.getJipletResourceCollection().getJipletNames()
.getJipletName().iterator();
while (i.hasNext() == true)
{
String jiplet = (String) i.next();
Jiplet j = context.findJiplet(jiplet);
if (j == null)
{
throw new JipletException(
"While creating security constraint for collection "
+ constraint.getJipletResourceCollection()
.getJipletResourceName()
+ " jiplet " + jiplet + " was not found");
}
synchronized (jipletMap)
{
String rname = auth.getRealm();
if (rname == null)
{
rname = JipletContainer.getInstance().getDefaultRealm();
}
Realm realm = JipletContainer.getInstance()
.findRealm(rname);
if (realm == null)
{
throw new JipletException(
"While creating security constraint for collection "
+ constraint
.getJipletResourceCollection()
.getJipletResourceName()
+ " realm " + rname + " was not found");
}
jipletMap.put(j, new Pair(realm, roles));
JipletLogger.info("Mapped security constraint for jiplet "
+ jiplet + " to realm " + rname);
}
}
}
}
public void destroy()
{
timer.cancel();
synchronized (jipletMap)
{
jipletMap.clear();
}
}
public Pair applySecurityPolicy(Jiplet jiplet, RequestEvent event)
{
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Request req = event.getRequest();
String method = req.getMethod();
if ((method.equals(Request.ACK) == true)
|| (method.equals(Request.CANCEL) == true))
{
// do not send a challenge for ACK and CANCEL messages
return new Pair(null, null);
}
Pair p = null;
synchronized (jipletMap)
{
p = (Pair) jipletMap.get(jiplet);
if (p == null)
{
// the jiplet does not have any security constraint
return new Pair(null, null);
}
}
Realm realm = (Realm) p.getFirst();
String[] roles = (String[]) p.getSecond();
String realm_name = null;
try
{
Thread.currentThread().setContextClassLoader(
realm.getClass().getClassLoader());
realm_name = realm.getRealmName();
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
// check if the request message contains an Authorization Header that
// belongs to this realm
String header_name = method.equals(Request.REGISTER) == true ? AuthorizationHeader.NAME
: ProxyAuthorizationHeader.NAME;
ListIterator iter = req.getHeaders(header_name);
boolean found = false;
while (iter.hasNext() == true)
{
found = true;
AuthorizationHeader header = (AuthorizationHeader) iter.next();
String call_id = ((CallIdHeader) req.getHeader(CallIdHeader.NAME))
.getCallId();
if (JipletLogger.isDebugEnabled() == true)
{
JipletLogger.debug("Found an authentication entry for call-id"
+ call_id + " for realm " + header.getRealm());
}
// check if the user has already been authenticated
String[] uroles = authorizations.findEntry(header.getRealm(),
call_id, header.getNonce(), header.getResponse());
if (uroles != null)
{
if (JipletLogger.isDebugEnabled() == true)
{
JipletLogger.debug("Authenticated call-id " + call_id
+ " from cached authentications");
}
// update the cached information with the new time-stamp
AuthorizationInfo ainfo = new AuthorizationInfo();
ainfo.setRealm(realm_name);
ainfo.setResponse(header.getResponse());
ainfo.setCallId(call_id);
ainfo.setNonce(header.getNonce());
authorizations.addEntry(ainfo, new Date());
// the user has a prior authentication for this realm, check if
// the user has proper authority
if (hasAuthorization(roles, uroles) == true)
{
return new Pair(new JipletPrincipal(header.getUsername()),
uroles);
}
else
{
// send a FORBIDDEN response
sendResponse(jiplet, event, Response.FORBIDDEN,
"You are not authorized to use this service", null);
return null;
}
}
else
{
// the user either does not prior authentication or the auth
// failed. Check if the authentication info that the user has
// sent can
// be authenticated.
try
{
// set the context class loader to that of the realm.
Thread.currentThread().setContextClassLoader(
realm.getClass().getClassLoader());
uroles = realm.authenticate(req.getMethod(), header);
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
if (uroles != null)
{
// add the information to the cached authorizations
AuthorizationInfo ainfo = new AuthorizationInfo();
ainfo.setRealm(realm_name);
ainfo.setResponse(header.getResponse());
ainfo.setCallId(call_id);
ainfo.setNonce(header.getNonce());
authorizations.addEntry(ainfo, new Date());
// the user has proper authentication for this realm, check
// if the user has proper authority
if (hasAuthorization(roles, uroles) == true)
{
return new Pair(new JipletPrincipal(header
.getUsername()), uroles);
}
else
{
// send a FORBIDDEN response
sendResponse(jiplet, event, Response.FORBIDDEN,
"You are not authorized to use this service",
null);
return null;
}
}
}
} // end while
if (found == false)
{
if (JipletLogger.isDebugEnabled() == true)
{
JipletLogger
.debug("Received a SIP request with no authentication header");
}
if (authOnLogout == false)
{
if (method.equals(Request.REGISTER) == true)
{
// get the expires header
ExpiresHeader expires = (ExpiresHeader) req
.getHeader(ExpiresHeader.NAME);
if ((expires != null) && (expires.getExpires() == 0))
{
FromHeader from = (FromHeader) req
.getHeader(FromHeader.NAME);
String name = ((SipURI) from.getAddress().getURI())
.getUser();
return new Pair(new JipletPrincipal(name), null);
}
}
}
}
// if we have reached here, it means that either the user has not has
// prior authentication or the credentials do not match, send a
// challenge.
String htype = method.equals(Request.REGISTER) == true ? WWWAuthenticateHeader.NAME
: ProxyAuthenticateHeader.NAME;
WWWAuthenticateHeader auth_header = null;
try
{
Thread.currentThread().setContextClassLoader(
realm.getClass().getClassLoader());
auth_header = realm.getAuthenticationHeader(jiplet, req, htype,
true);
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
if (auth_header == null)
{
// the realm is indicating that it does not want to challenge this
// user.
// Send a FORBIDDEN response
sendResponse(jiplet, event, Response.FORBIDDEN,
"You are not authorized to use this service", null);
return null;
}
// send a challenge
int status = method.equals(Request.REGISTER) == true ? Response.UNAUTHORIZED
: Response.PROXY_AUTHENTICATION_REQUIRED;
sendResponse(jiplet, event, status, "Please provide your credentials",
auth_header);
return null;
}
private boolean hasAuthorization(String[] required, String[] has)
{
for (int i = 0; i < has.length; i++)
{
for (int j = 0; j < required.length; j++)
{
if (has[i].equals(required[j]) == true)
{
return true;
}
}
}
return false;
}
private void sendResponse(Jiplet jiplet, RequestEvent event, int response,
String reason, WWWAuthenticateHeader authHeader)
{
ServerTransaction transaction = event.getServerTransaction();
try
{
Response resp = jiplet.getMessageFactory().createResponse(response,
event.getRequest());
if (reason != null)
{
resp.setReasonPhrase(reason);
}
if (authHeader != null)
{
resp.addHeader(authHeader);
}
if (transaction != null)
{
transaction.sendResponse(resp);
}
else
{
SipProvider provider = (SipProvider) event.getSource();
provider.sendResponse(resp);
}
}
catch (Exception e)
{
JipletLogger
.error("SecurityConstraint for context: "
+ context.getContext()
+ " could not send response message in response to the SIP request message: "
+ " jiplet: " + jiplet.getName() + ", status: "
+ response + " exception: "
+ e.getClass().getName() + ": " + e.getMessage());
}
}
/*
* @see java.util.TimerTask#run()
*/
public void run()
{
long expire = (new Date()).getTime() - cachePeriod;
authorizations.removeEntries(new Date(expire));
}
/**
* @return Returns the cachePeriod.
*/
protected long getCachePeriod()
{
return cachePeriod;
}
/**
* @param cachePeriod
* The cachePeriod to set.
*/
public void setCachePeriod(long cachePeriod)
{
this.cachePeriod = cachePeriod;
}
public Pair findSecurityConstraint(Jiplet jiplet)
{
synchronized (jipletMap)
{
return (Pair) jipletMap.get(jiplet);
}
}
protected boolean isAuthOnLogout()
{
return authOnLogout;
}
public void setAuthOnLogout(boolean authOnLogout)
{
this.authOnLogout = authOnLogout;
}
public void removeRealm(Realm realm)
{
synchronized (jipletMap)
{
Iterator i = jipletMap.entrySet().iterator();
while (i.hasNext() == true)
{
Map.Entry entry = (Map.Entry) i.next();
Jiplet jiplet = (Jiplet) entry.getKey();
Pair p = (Pair) entry.getValue();
Realm r = (Realm) p.getFirst();
String rname = null;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(
r.getClass().getClassLoader());
rname = realm.getRealmName();
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
if (r.getRealmName().equals(rname) == true)
{
JipletLogger
.warn("Removing realm "
+ realm.getRealmName()
+ " from jiplet "
+ jiplet.getName()
+ ". No auth. check will be performed for this jiplet anymore.");
i.remove();
}
}
}
}
}