package org.mobicents.slee.sippresence.server.subscription.rules; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; import javax.xml.bind.JAXBElement; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; import org.openxdm.xcap.client.appusage.presrules.jaxb.ProvideAllAttributes; import org.openxdm.xcap.client.appusage.presrules.jaxb.ProvideDevicePermission; import org.openxdm.xcap.client.appusage.presrules.jaxb.ProvidePersonPermission; import org.openxdm.xcap.client.appusage.presrules.jaxb.ProvideServicePermission; import org.openxdm.xcap.client.appusage.presrules.jaxb.UnknownBooleanPermission; import org.openxdm.xcap.client.appusage.presrules.jaxb.commonpolicy.ActionsType; import org.openxdm.xcap.client.appusage.presrules.jaxb.commonpolicy.ExceptType; import org.openxdm.xcap.client.appusage.presrules.jaxb.commonpolicy.IdentityType; import org.openxdm.xcap.client.appusage.presrules.jaxb.commonpolicy.ManyType; import org.openxdm.xcap.client.appusage.presrules.jaxb.commonpolicy.OneType; import org.openxdm.xcap.client.appusage.presrules.jaxb.commonpolicy.RuleType; import org.openxdm.xcap.client.appusage.presrules.jaxb.commonpolicy.Ruleset; import org.openxdm.xcap.client.appusage.presrules.jaxb.commonpolicy.SphereType; import org.openxdm.xcap.client.appusage.presrules.jaxb.commonpolicy.TransformationsType; import org.openxdm.xcap.client.appusage.presrules.jaxb.commonpolicy.ValidityType; public class RulesetProcessor { private String subscriber; private String notifier; private PublishedSphereSource publishedSphereSource; private OMAPresRule combinedRule; public RulesetProcessor(String subscriber, String notifier, Ruleset ruleset, PublishedSphereSource publishedSphereSource) { this.subscriber = subscriber; this.notifier = notifier; this.publishedSphereSource = publishedSphereSource; processRuleset(ruleset); } public String getSubscriber() { return subscriber; } public OMAPresRule getCombinedRule() { return combinedRule; } // ----------------------------- RULE SET PROCESSING --------------- private void processRuleset(Ruleset ruleset) { for (RuleType ruleType: ruleset.getRule()) { // process actions SubHandlingAction subHandling = processActions(ruleType); if (subHandling == null) { // continue, this rule has nothing to do with pres-rules auth continue; } // process conditions boolean permissionGranted = processConditions(ruleType); if (permissionGranted) { // process transformations OMAPresRule omaPresRule = processTransformations(ruleType); if (omaPresRule != null) { // set sub-handling omaPresRule.setSubHandling(subHandling); // combine rule if (combinedRule == null) { combinedRule = omaPresRule; } else { combinedRule.combine(omaPresRule); } } } } if(combinedRule == null) { combinedRule = new OMAPresRule(); } } /** * process rule's actions * @param ruleType * @return the sub-handling value found */ private SubHandlingAction processActions(RuleType ruleType) { ActionsType actionsType = ruleType.getActions(); if (actionsType != null) { List anys = actionsType.getAny(); if(anys.size() == 1 && anys.get(0) instanceof JAXBElement) { JAXBElement element = (JAXBElement) anys.get(0); if (element.getName().getLocalPart().equals("sub-handling")) { String subHandlingValue = (String) element.getValue(); // 0 is block, 10 is confirm, 20 is polite-block (not supported yet), 30 is allow if (subHandlingValue.equals("allow") || subHandlingValue.equals("30")) { return SubHandlingAction.allow; } else if (subHandlingValue.equals("polite-block") || subHandlingValue.equals("20")) { return SubHandlingAction.politeblock; } else if (subHandlingValue.equals("confirm") || subHandlingValue.equals("10")) { return SubHandlingAction.confirm; } else if (subHandlingValue.equals("block") || subHandlingValue.equals("0")) { return SubHandlingAction.block; } } } } return null; } /** * process rules's conditions * @param ruleType * @return true if permission is granted to apply actions and transformations of the rule */ private boolean processConditions(RuleType ruleType) { String subscriberDomain = null; // all conditions must evaluate to true List identityOrSphereOrValidityObjectList = ruleType.getConditions().getIdentityOrSphereOrValidity(); if (identityOrSphereOrValidityObjectList.isEmpty()) { return false; } else { for (Object identityOrSphereOrValidityObject : identityOrSphereOrValidityObjectList) { if (identityOrSphereOrValidityObject instanceof JAXBElement) { JAXBElement identityOrSphereOrValidity = (JAXBElement) identityOrSphereOrValidityObject; if (identityOrSphereOrValidity.getValue() instanceof IdentityType) { IdentityType identityType = (IdentityType) identityOrSphereOrValidity.getValue(); // identity permission is granted if one sub-clause matches subscriber boolean idPermission = false; for (Object oneOrManyOrAnyObject : identityType.getOneOrManyOrAny()) { JAXBElement oneOrManyOrAny = (JAXBElement) oneOrManyOrAnyObject; if (oneOrManyOrAny.getValue() instanceof OneType) { OneType oneType = (OneType) oneOrManyOrAny.getValue(); if (subscriber.equals(oneType.getId())) { // found identity of subscriber idPermission = true; break; } } else if (oneOrManyOrAny.getValue() instanceof ManyType) { ManyType manyType = (ManyType) oneOrManyOrAny.getValue(); if (subscriberDomain == null) { int i = subscriber.indexOf('@'); if (i>0) { subscriberDomain = subscriber.substring(i+1); } } if (manyType.getDomain() == null || domainsMatch(subscriberDomain,manyType.getDomain())) { boolean exceptNotFound = true; for(Object exceptOrAnyObject: manyType.getExceptOrAny()) { JAXBElement exceptOrAny = (JAXBElement) exceptOrAnyObject; if (exceptOrAny.getValue() instanceof ExceptType) { ExceptType exceptType = (ExceptType) exceptOrAny.getValue(); if (subscriber.equals(exceptType.getId())) { // found subscriber as exception in domain exceptNotFound = false; break; } else if (domainsMatch(subscriberDomain, exceptType.getDomain())) { // found subscriber domain as exception in domain exceptNotFound = false; break; } } } if (exceptNotFound) { idPermission = true; break; } } } } if (!idPermission) { return false; } } else if (identityOrSphereOrValidity.getValue() instanceof SphereType) { SphereType sphereType = (SphereType) identityOrSphereOrValidity.getValue(); // we need current sphere published by notifier String sphere = publishedSphereSource.getSphere(notifier); if (sphereType.getValue() == null || spheresMatch(sphere, sphereType.getValue())) { return false; } } else if (identityOrSphereOrValidity.getValue() instanceof ValidityType) { ValidityType validityType = (ValidityType) identityOrSphereOrValidity.getValue(); /* * The <validity> element is the third condition element * specified in this document. It expresses the rule * validity period by two attributes, a starting and an * ending time. The validity condition is TRUE if the * current time is greater than or equal to at least one * <from> child, but less than the <until> child after it. * This represents a logical OR operation across each <from> * and <until> pair. Times are expressed in XML dateTime * format. */ if (validityType.getFromAndUntil().size()%2 == 0) { // verified we have a pair number of subclauses } boolean valid = false; XMLGregorianCalendar calendar = null; try { calendar = DatatypeFactory.newInstance() .newXMLGregorianCalendar(new GregorianCalendar()); } catch (DatatypeConfigurationException e) { System.err.println("Failed to create calendar to verify pres-rules condition validity"); e.printStackTrace(); } if (calendar != null) { for (Iterator<JAXBElement<XMLGregorianCalendar>> iterator = validityType.getFromAndUntil().iterator(); iterator.hasNext();) { JAXBElement<XMLGregorianCalendar> fromElement = iterator.next(); JAXBElement<XMLGregorianCalendar> untilElement = iterator.next(); if (fromElement.getName().getLocalPart().equals("from") && untilElement.getName().getLocalPart().equals("until")) { if (fromElement.getValue().compare(calendar) < 1 && untilElement.getValue().compare(calendar) > -1) { valid = true; break; } } else { // invalid condition, fail valid = false; break; } } } if (!valid) { return false; } } } } // no conditions evaluated to false; return true; } } /** * process rule's transformations * * @param ruleType * @return a {@link OMAPresRule} filled with transformations */ private OMAPresRule processTransformations(RuleType ruleType) { // conditions applies to subscription, let's get rule transformations TransformationsType transformationsType = ruleType.getTransformations(); if (transformationsType != null) { // create rule OMAPresRule rule = new OMAPresRule(); // fill transformations for(Object transformationObject: transformationsType.getAny()) { if(transformationObject instanceof ProvideAllAttributes) { rule.setProvideAllAttributes(true); } /* else if(transformationObject instanceof ProvideDevicePermission) { rule.processDevicePermission((ProvideDevicePermission)transformationObject); } else if(transformationObject instanceof ProvidePersonPermission) { rule.processPersonPermission((ProvidePersonPermission) transformationObject); } else if(transformationObject instanceof ProvideServicePermission) { rule.processServicePermission((ProvideServicePermission) transformationObject); } else if(transformationObject instanceof UnknownBooleanPermission) { UnknownBooleanPermission unknownBooleanPermission = (UnknownBooleanPermission) transformationObject; rule.getUnknownBooleanAttributes().add(new UnknownBooleanAttributeTransformation(unknownBooleanPermission.getName(),unknownBooleanPermission.getNs())); } */ else if(transformationObject instanceof JAXBElement) { JAXBElement element = (JAXBElement) transformationObject; if (element.getName().getNamespaceURI().equals("urn:oma:xml:prs:pres-rules")) { // oma transformations if (element.getName().getLocalPart().equals("provide-registration-state")) { rule.setProvideRegistrationState(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-network-availability")) { rule.setProvideNetworkAvailability(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-willingness")) { rule.setProvideWillingness(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-barring-state")) { rule.setProvideBarringState(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-session-participation")) { rule.setProvideSessionParticipation(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("service-id")) { rule.getServiceIDs().add((String)element.getValue()); } else if (element.getName().getLocalPart().equals("provide-geopriv")) { try { rule.setProvideGeopriv(GeoPrivTransformation.valueOf((String)element.getValue())); } catch (Exception e) { System.err.println("Failed to parse provide-geopriv value in transformation"); e.printStackTrace(); } } // unknown transformation, ignore } else if (element.getName().getNamespaceURI().equals("urn:ietf:params:xml:ns:pres-rules")) { // ietf transformations if(element.getName().getLocalPart().equals("provide-devices")) { rule.processDevicePermission((ProvideDevicePermission)element.getValue()); } else if(element.getName().getLocalPart().equals("provide-persons")) { rule.processPersonPermission((ProvidePersonPermission)element.getValue()); } else if(element.getName().getLocalPart().equals("provide-services")) { rule.processServicePermission((ProvideServicePermission)element.getValue()); } else if(element.getName().getLocalPart().equals("provide-unknown-attribute")) { UnknownBooleanPermission unknownBooleanPermission = (UnknownBooleanPermission) element.getValue(); rule.getUnknownBooleanAttributes().add(new UnknownBooleanAttributeTransformation(unknownBooleanPermission.getName(),unknownBooleanPermission.getNs())); } else if (element.getName().getLocalPart().equals("provide-place-is")) { rule.setProvidePlaceIs(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-privacy")) { rule.setProvidePrivacy(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-class")) { rule.setProvideClass(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-place-type")) { rule.setProvidePlaceType(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-relationship")) { rule.setProvideRelationship(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-mood")) { rule.setProvideMood(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-activities")) { rule.setProvideActivities(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-sphere")) { rule.setProvideSphere(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-user-input")) { try { rule.setProvideUserInput(UserInputTransformation.valueOf((String)element.getValue())); } catch (Exception e) { System.err.println("Failed to parse provide-user-input value in transformation"); e.printStackTrace(); } } else if (element.getName().getLocalPart().equals("provide-time-offset")) { rule.setProvideTimeOffset(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-note")) { rule.setProvideNote(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-deviceID")) { rule.setProvideDeviceID(((Boolean)element.getValue()).booleanValue()); } else if (element.getName().getLocalPart().equals("provide-status-icon")) { rule.setProvideStatusIcon(((Boolean)element.getValue()).booleanValue()); } // else unknown transformation, ignore } // else unknown transformation, ignore } // else unknown transformation, ignore } return rule; } else { // no transformations return null; } } private boolean domainsMatch(String domain,String conditionDomain) { // conditionDomain can be null // FIXME proper domain matching /* * Common policy MUST either use UTF-8 or UTF-16 to store domain names * in the 'domain' attribute. For non-IDNs (Internationalized Domain * Names), lowercase ASCII SHOULD be used. For the comparison operation * between the value stored in the 'domain' attribute and the domain * value provided via the using protocol (referred to as "protocol * domain identifier"), the following rules are applicable: * * 1. Translate percent-encoding for either string. * * 2. Convert both domain strings using the ToASCII operation described * in RFC 3490 [3]. * * 3. Compare the two domain strings for ASCII equality, for each label. * If the string comparison for each label indicates equality, the * comparison succeeds. Otherwise, the domains are not equal. * * If the conversion fails in step (2), the domains are not equal. * */ return domain.equals(conditionDomain); } private boolean spheresMatch(String sphere, String conditionSphere) { // sphere can be null /* * The <sphere> element belongs to the group of condition elements. It * can be used to indicate a state (e.g., 'work', 'home', 'meeting', * 'travel') the PT is currently in. A sphere condition matches only if * the PT is currently in the state indicated. The state may be conveyed * by manual configuration or by some protocol. For example, RPID [10] * provides the ability to inform the PS of its current sphere. The * application domain needs to describe in more detail how the sphere * state is determined. Switching from one sphere to another causes a * switch between different modes of visibility. As a result, different * subsets of rules might be applicable. * * The content of the 'value' attribute of the <sphere> element MAY * contain more than one token. The individual tokens MUST be separated * by a blank character. A logical OR is used for the matching the * tokens against the sphere settings of the PT. As an example, if the * content of the 'value' attribute in the sphere attribute contains two * tokens 'work' and 'home' then this part of the rule matches if the * sphere for a particular PT is either 'work' OR 'home'. To compare the * content of the 'value' attribute in the <sphere> element with the * stored state information about the PT's sphere setting a * case-insensitive string comparison MUST be used for each individual * token. There is neither a registry for these values nor a language- * specific indication of the sphere content. As such, the tokens are * treated as opaque strings. * */ String[] conditionSphereTokens = conditionSphere.split(" "); for (String conditionSphereToken : conditionSphereTokens){ if (conditionSphereToken.equalsIgnoreCase(sphere)) { return true; } } return false; } }