/*
 *
 * Copyright (c) 2006 The Apache Software Foundation
 *
 * 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.
 *
 */

#include "qpid/acl/AclData.h"
#include "qpid/log/Statement.h"
#include "qpid/sys/IntegerTypes.h"
#include <boost/lexical_cast.hpp>

namespace qpid {
namespace acl {

    //
    // Instantiate the keyword strings
    //
    const std::string AclData::ACL_KEYWORD_USER_SUBST        = "${user}";
    const std::string AclData::ACL_KEYWORD_DOMAIN_SUBST      = "${domain}";
    const std::string AclData::ACL_KEYWORD_USERDOMAIN_SUBST  = "${userdomain}";
    const std::string AclData::ACL_KEYWORD_ALL               = "all";
    const std::string AclData::ACL_KEYWORD_ACL               = "acl";
    const std::string AclData::ACL_KEYWORD_GROUP             = "group";
    const std::string AclData::ACL_KEYWORD_QUOTA             = "quota";
    const std::string AclData::ACL_KEYWORD_QUOTA_CONNECTIONS = "connections";
    const std::string AclData::ACL_KEYWORD_QUOTA_QUEUES      = "queues";
    const char        AclData::ACL_SYMBOL_WILDCARD           = '*';
    const std::string AclData::ACL_KEYWORD_WILDCARD          = "*";
    const char        AclData::ACL_SYMBOL_LINE_CONTINUATION  = '\\';
    const std::string AclData::ACL_KEYWORD_DEFAULT_EXCHANGE  = "amq.default";

    //
    // constructor
    //
    AclData::AclData():
        decisionMode(qpid::acl::DENY),
        transferAcl(false),
        aclSource("UNKNOWN"),
        connQuotaRulesExist(false),
        connQuotaRuleSettings(new quotaRuleSet),
        queueQuotaRulesExist(false),
        queueQuotaRuleSettings(new quotaRuleSet)
    {
        for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++)
        {
            actionList[cnt]=0;
        }
    }


    //
    // clear
    //
    void AclData::clear ()
    {
        for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++)
        {
            if (actionList[cnt])
            {
                for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++)
                    delete actionList[cnt][cnt1];
            }
            delete[] actionList[cnt];
        }
        transferAcl = false;
        connQuotaRulesExist = false;
        connQuotaRuleSettings->clear();
        queueQuotaRulesExist = false;
        queueQuotaRuleSettings->clear();
    }


    //
    // matchProp
    //
    // Compare a rule's property name with a lookup name,
    // The rule's name may contain a trailing '*' to specify a wildcard match.
    //
    bool AclData::matchProp(const std::string& ruleStr,
                            const std::string& lookupStr)
    {
        // allow wildcard on the end of rule strings...
        if (ruleStr.data()[ruleStr.size()-1]==ACL_SYMBOL_WILDCARD)
        {
            return ruleStr.compare(0,
                                   ruleStr.size()-1,
                                   lookupStr,
                                   0,
                                   ruleStr.size()-1  ) == 0;
        }
        else
        {
            return ruleStr.compare(lookupStr) == 0;
        }
    }


    //
    // lookup
    //
    // The ACL main business logic function of matching rules and declaring
    // an allow or deny result.
    //
    AclResult AclData::lookup(
        const std::string&               id,
        const Action&                    action,
        const ObjectType&                objType,
        const std::string&               name,
        std::map<Property, std::string>* params)
    {
        QPID_LOG(debug, "ACL: Lookup for id:" << id
                    << " action:" << AclHelper::getActionStr((Action) action)
                    << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType)
                    << " name:" << name
                    << " with params " << AclHelper::propertyMapToString(params));

        // A typical log looks like:
        // ACL: Lookup for id:bob@QPID action:create objectType:queue name:q2
        //  with params { durable=false passive=false autodelete=false
        //  exclusive=false alternate= policytype= maxqueuesize=0
        //  maxqueuecount=0 }

        // Default result is blanket decision mode for the entire ACL list.
        AclResult aclresult = decisionMode;

        // Test for lists of rules at the intersection of the Action & Object
        if (actionList[action] && actionList[action][objType])
        {
            // Find the list of rules for this actorId
            AclData::actObjItr itrRule = actionList[action][objType]->find(id);

            // If individual actorId not found then find a rule set for '*'.
            if (itrRule == actionList[action][objType]->end())
                itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD);

            if (itrRule != actionList[action][objType]->end())
            {
                // A list of rules exists for this actor/action/object tuple.
                // Iterate the rule set to search for a matching rule.
                ruleSetItr rsItr = itrRule->second.end();
                for (int cnt = itrRule->second.size(); cnt != 0; cnt--)
                {
                    rsItr--;

                    QPID_LOG(debug, "ACL: checking rule " <<  rsItr->toString());

                    bool match = true;
                    bool limitChecked = true;

                    // Iterate this rule's properties. A 'match' is true when
                    // all of the rule's properties are found to be satisfied
                    // in the lookup param list. The lookup may specify things
                    // (they usually do) that are not in the rule properties but
                    // these things don't interfere with the rule match.

                    for (specPropertyMapItr rulePropMapItr  = rsItr->props.begin();
                                           (rulePropMapItr != rsItr->props.end()) && match;
                                            rulePropMapItr++)
                    {
                        // The rule property map's NAME property is given in
                        // the calling args and not in the param map.
                        if (rulePropMapItr->first == acl::SPECPROP_NAME)
                        {
                            // substitute user name into object name
                            bool result;
                            if (rsItr->ruleHasUserSub[PROP_NAME]) {
                                std::string sName(rulePropMapItr->second);
                                substituteUserId(sName, id);
                                result = matchProp(sName, name);
                            } else {
                                result = matchProp(rulePropMapItr->second, name);
                            }

                            if (result)
                            {
                                QPID_LOG(debug, "ACL: lookup name '" << name
                                    << "' matched with rule name '"
                                    << rulePropMapItr->second << "'");
                            }
                            else
                            {
                                match = false;
                                QPID_LOG(debug, "ACL: lookup name '" << name
                                    << "' didn't match with rule name '"
                                    << rulePropMapItr->second << "'");
                            }
                        }
                        else
                        {
                            if (params)
                            {
                                // The rule's property map non-NAME properties
                                //  found in the lookup's params list.
                                // In some cases the param's index is not the same
                                //  as rule's index.
                                propertyMapItr lookupParamItr;
                                switch (rulePropMapItr->first)
                                {
                                case acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT:
                                case acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT:
                                    lookupParamItr = params->find(PROP_MAXQUEUECOUNT);
                                    break;

                                case acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT:
                                case acl::SPECPROP_MAXQUEUESIZELOWERLIMIT:
                                    lookupParamItr = params->find(PROP_MAXQUEUESIZE);
                                    break;

                                case acl::SPECPROP_MAXFILECOUNTUPPERLIMIT:
                                case acl::SPECPROP_MAXFILECOUNTLOWERLIMIT:
                                    lookupParamItr = params->find(PROP_MAXFILECOUNT);
                                    break;

                                case acl::SPECPROP_MAXFILESIZEUPPERLIMIT:
                                case acl::SPECPROP_MAXFILESIZELOWERLIMIT:
                                    lookupParamItr = params->find(PROP_MAXFILESIZE);
                                    break;

                                default:
                                    lookupParamItr = params->find((Property)rulePropMapItr->first);
                                    break;
                                };

                                if (lookupParamItr == params->end())
                                {
                                    // Now the rule has a specified property
                                    // that does not exist in the caller's
                                    // lookup params list.
                                    // This rule does not match.
                                    match = false;
                                    QPID_LOG(debug, "ACL: lookup parameter map doesn't contain the rule property '"
                                        << AclHelper::getPropertyStr(rulePropMapItr->first) << "'");
                                }
                                else
                                {
                                    // Now account for the business of rules
                                    // whose property indexes are mismatched.
                                    switch (rulePropMapItr->first)
                                    {
                                    case acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT:
                                    case acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT:
                                    case acl::SPECPROP_MAXFILECOUNTUPPERLIMIT:
                                    case acl::SPECPROP_MAXFILESIZEUPPERLIMIT:
                                        limitChecked &=
                                            compareInt(
                                                rulePropMapItr->first,
                                                boost::lexical_cast<std::string>(rulePropMapItr->second),
                                                boost::lexical_cast<std::string>(lookupParamItr->second),
                                                true);
                                        break;

                                    case acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT:
                                    case acl::SPECPROP_MAXQUEUESIZELOWERLIMIT:
                                    case acl::SPECPROP_MAXFILECOUNTLOWERLIMIT:
                                    case acl::SPECPROP_MAXFILESIZELOWERLIMIT:
                                        limitChecked &=
                                            compareInt(
                                                rulePropMapItr->first,
                                                boost::lexical_cast<std::string>(rulePropMapItr->second),
                                                boost::lexical_cast<std::string>(lookupParamItr->second),
                                                false);
                                        break;

                                    default:
                                        bool result;
                                        if ((SPECPROP_ALTERNATE  == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_ALTERNATE])  ||
                                            (SPECPROP_QUEUENAME  == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_QUEUENAME]))
                                        {
                                            // These properties are allowed to have username substitution
                                            std::string sName(rulePropMapItr->second);
                                            substituteUserId(sName, id);
                                            result = matchProp(sName, lookupParamItr->second);
                                        }
                                        else if (SPECPROP_ROUTINGKEY == rulePropMapItr->first)
                                        {
                                            // Routing key is allowed to have username substitution
                                            // and it gets topic exchange matching
                                            if (rsItr->ruleHasUserSub[PROP_ROUTINGKEY])
                                            {
                                                std::string sKey(lookupParamItr->second);
                                                substituteKeywords(sKey, id);
                                                result = rsItr->matchRoutingKey(sKey);
                                            }
                                            else
                                            {
                                                result = rsItr->matchRoutingKey(lookupParamItr->second);
                                            }
                                        }
                                        else
                                        {
                                            // Rules without substitution
                                            result = matchProp(rulePropMapItr->second, lookupParamItr->second);
                                        }

                                        if (result)
                                        {
                                            QPID_LOG(debug, "ACL: the pair("
                                                << AclHelper::getPropertyStr(lookupParamItr->first)
                                                << "," << lookupParamItr->second
                                                << ") given in lookup matched the pair("
                                                << AclHelper::getPropertyStr(rulePropMapItr->first) << ","
                                                << rulePropMapItr->second
                                                << ") given in the rule");
                                        }
                                        else
                                        {
                                            match = false;
                                            QPID_LOG(debug, "ACL: the pair("
                                                << AclHelper::getPropertyStr(lookupParamItr->first)
                                                << "," << lookupParamItr->second
                                                << ") given in lookup doesn't match the pair("
                                                << AclHelper::getPropertyStr(rulePropMapItr->first)
                                                << "," << rulePropMapItr->second
                                                << ") given in the rule");
                                        }
                                        break;
                                    };
                                }
                            }
                            else
                            {
                                // params don't exist.
                            }
                        }
                    }
                    if (match)
                    {
                        aclresult = rsItr->ruleMode;
                        if (!limitChecked)
                        {
                            // Now a lookup matched all rule properties but one
                            //  of the numeric limit checks has failed.
                            // Demote allow rules to corresponding deny rules.
                            switch (aclresult)
                            {
                            case acl::ALLOW:
                                aclresult = acl::DENY;
                                break;
                            case acl::ALLOWLOG:
                                aclresult = acl::DENYLOG;
                                break;
                            default:
                                break;
                            };
                        }
                        QPID_LOG(debug,"ACL: Successful match, the decision is:"
                            << AclHelper::getAclResultStr(aclresult));
                        return aclresult;
                    }
                    else
                    {
                        // This rule did not match the requested lookup and
                        // does not contribute to an ACL decision.
                    }
                }
            }
            else
            {
                // The Action-Object list has entries but not for this actorId
                // nor for *.
            }
        }
        else
        {
            // The Action-Object list has no entries.
        }

        QPID_LOG(debug,"ACL: No successful match, defaulting to the decision mode "
            << AclHelper::getAclResultStr(aclresult));
        return aclresult;
    }


    //
    // lookup
    //
    // The ACL main business logic function of matching rules and declaring
    // an allow or deny result. This lookup is the fastpath per-message
    // lookup to verify if a user is allowed to publish to an exchange with
    // a given key.
    //
    AclResult AclData::lookup(
        const std::string&              id,
        const Action&                   action,
        const ObjectType&               objType,
        const std::string& /*Exchange*/ name,
        const std::string&              routingKey)
    {

        QPID_LOG(debug, "ACL: Lookup for id:" << id
            << " action:" << AclHelper::getActionStr((Action) action)
            << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType)
            << " exchange name:" << name
            << " with routing key " << routingKey);

        AclResult aclresult = decisionMode;

        if (actionList[action] && actionList[action][objType]){
            AclData::actObjItr itrRule = actionList[action][objType]->find(id);

            if (itrRule == actionList[action][objType]->end())
                itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD);

            if (itrRule != actionList[action][objType]->end() )
            {
                // Found a rule list for this user-action-object set.
                // Search the rule list for a matching rule.
                ruleSetItr rsItr = itrRule->second.end();
                for (int cnt = itrRule->second.size(); cnt != 0; cnt--)
                {
                    rsItr--;

                    QPID_LOG(debug, "ACL: checking rule " <<  rsItr->toString());

                    // Search on exchange name and routing key only if specfied in rule.
                    bool match =true;
                    if (rsItr->pubExchNameInRule)
                    {
                        // substitute user name into object name
                        bool result;

                        if (rsItr->ruleHasUserSub[PROP_NAME]) {
                            std::string sName(rsItr->pubExchName);
                            substituteUserId(sName, id);
                            result = matchProp(sName, name);
                        } 
                        else if (rsItr->pubExchNameMatchesBlank) 
                        {
                            result = name.empty();
                        }
                        else
                        {
                            result = matchProp(rsItr->pubExchName, name);
                        }

                        if (result)
                        {
                            QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup exchange name '"
                                << name << "' matched with rule name '"
                                << rsItr->pubExchName << "'");
                        }
                        else
                        {
                            match= false;
                            QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup exchange name '"
                                << name << "' did not match with rule name '"
                                << rsItr->pubExchName << "'");
                        }
                    }

                    if (match && rsItr->pubRoutingKeyInRule)
                    {
                        if ((routingKey.find(ACL_KEYWORD_USER_SUBST, 0)       != std::string::npos) ||
                            (routingKey.find(ACL_KEYWORD_DOMAIN_SUBST, 0)     != std::string::npos) ||
                            (routingKey.find(ACL_KEYWORD_USERDOMAIN_SUBST, 0) != std::string::npos))
                        {
                            // The user is not allowed to present a routing key with the substitution key in it
                            QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum <<
                                " User-specified routing key has substitution wildcard:" << routingKey
                                << ". Rule match prohibited.");
                            match = false;
                        }
                        else
                        {
                            bool result;
                            if (rsItr->ruleHasUserSub[PROP_ROUTINGKEY]) {
                                std::string sKey(routingKey);
                                substituteKeywords(sKey, id);
                                result = rsItr->matchRoutingKey(sKey);
                            } else {
                                result = rsItr->matchRoutingKey(routingKey);
                            }

                            if (result)
                            {
                                QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup key name '"
                                    << routingKey << "' matched with rule routing key '"
                                    << rsItr->pubRoutingKey << "'");
                            }
                            else
                            {
                                QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup key name '"
                                    << routingKey << "' did not match with rule routing key '"
                                    << rsItr->pubRoutingKey << "'");
                                match = false;
                            }
                        }
                    }

                    if (match){
                        aclresult = rsItr->ruleMode;
                        QPID_LOG(debug,"ACL: Rule: " << rsItr->rawRuleNum << " Successful match, the decision is:"
                            << AclHelper::getAclResultStr(aclresult));
                        return aclresult;
                    }
                }
            }
        }
        QPID_LOG(debug,"ACL: No successful match, defaulting to the decision mode "
            << AclHelper::getAclResultStr(aclresult));
        return aclresult;

    }



    //
    //
    //
    void AclData::setConnQuotaRuleSettings (
                bool rulesExist, boost::shared_ptr<quotaRuleSet> quotaPtr)
    {
        connQuotaRulesExist = rulesExist;
        connQuotaRuleSettings = quotaPtr;
    }


    //
    // getConnQuotaForUser
    //
    // Return the true or false value of connQuotaRulesExist,
    //  indicating whether any kind of lookup was done or not.
    //
    // When lookups are performed return the result value of
    //  1. The user's setting else
    //  2. The 'all' user setting else
    //  3. Zero
    // When lookups are not performed then return a result value of Zero.
    //
    bool AclData::getConnQuotaForUser(const std::string& theUserName,
                                      uint16_t* theResult) const {
        if (connQuotaRulesExist) {
            // look for this user explicitly
            quotaRuleSetItr nameItr = (*connQuotaRuleSettings).find(theUserName);
            if (nameItr != (*connQuotaRuleSettings).end()) {
                QPID_LOG(trace, "ACL: Connection quota for user " << theUserName
                    << " explicitly set to : " << (*nameItr).second);
                *theResult = (*nameItr).second;
            } else {
                // Look for the 'all' user
                nameItr = (*connQuotaRuleSettings).find(ACL_KEYWORD_ALL);
                if (nameItr != (*connQuotaRuleSettings).end()) {
                    QPID_LOG(trace, "ACL: Connection quota for user " << theUserName
                        << " chosen through value for 'all' : " << (*nameItr).second);
                    *theResult = (*nameItr).second;
                } else {
                    // Neither userName nor "all" found.
                    QPID_LOG(trace, "ACL: Connection quota for user " << theUserName
                        << " absent in quota settings. Return value : 0");
                    *theResult = 0;
                }
            }
        } else {
            // Rules do not exist
            QPID_LOG(trace, "ACL: Connection quota for user " << theUserName
                << " unavailable; quota settings are not specified. Return value : 0");
            *theResult = 0;
        }
        return connQuotaRulesExist;
    }

    //
    //
    //
    void AclData::setQueueQuotaRuleSettings (
                bool rulesExist, boost::shared_ptr<quotaRuleSet> quotaPtr)
    {
        queueQuotaRulesExist = rulesExist;
        queueQuotaRuleSettings = quotaPtr;
    }


    //
    // getQueueQuotaForUser
    //
    // Return the true or false value of queueQuotaRulesExist,
    //  indicating whether any kind of lookup was done or not.
    //
    // When lookups are performed return the result value of
    //  1. The user's setting else
    //  2. The 'all' user setting else
    //  3. Zero
    // When lookups are not performed then return a result value of Zero.
    //
    bool AclData::getQueueQuotaForUser(const std::string& theUserName,
                                      uint16_t* theResult) const {
        if (queueQuotaRulesExist) {
            // look for this user explicitly
            quotaRuleSetItr nameItr = (*queueQuotaRuleSettings).find(theUserName);
            if (nameItr != (*queueQuotaRuleSettings).end()) {
                QPID_LOG(trace, "ACL: Queue quota for user " << theUserName
                    << " explicitly set to : " << (*nameItr).second);
                *theResult = (*nameItr).second;
            } else {
                // Look for the 'all' user
                nameItr = (*queueQuotaRuleSettings).find(ACL_KEYWORD_ALL);
                if (nameItr != (*queueQuotaRuleSettings).end()) {
                    QPID_LOG(trace, "ACL: Queue quota for user " << theUserName
                        << " chosen through value for 'all' : " << (*nameItr).second);
                    *theResult = (*nameItr).second;
                } else {
                    // Neither userName nor "all" found.
                    QPID_LOG(trace, "ACL: Queue quota for user " << theUserName
                        << " absent in quota settings. Return value : 0");
                    *theResult = 0;
                }
            }
        } else {
            // Rules do not exist
            QPID_LOG(trace, "ACL: Queue quota for user " << theUserName
                << " unavailable; quota settings are not specified. Return value : 0");
            *theResult = 0;
        }
        return queueQuotaRulesExist;
    }


    //
    //
    //
    AclData::~AclData()
    {
        clear();
    }


    //
    // Limit check an int limit
    //
    bool AclData::compareInt(const qpid::acl::SpecProperty theProperty,
                             const std::string             theAclValue,
                             const std::string             theLookupValue,
                             bool                          theMaxFlag)
    {
        uint64_t aclRuleValue (0);
        uint64_t lookupValue  (0);

        QPID_LOG(debug, "ACL: "
            << (theMaxFlag ? "Upper" : "Lower") << "-limit comparison for property "
            << AclHelper::getPropertyStr(theProperty)
            << ". Success if lookup(" << theLookupValue
            << ") "
            << (theMaxFlag ? "<=" : ">=") << " rule(" << theAclValue << ")");

        try
        {
            aclRuleValue = boost::lexical_cast<uint64_t>(theAclValue);
        }
        catch(const boost::bad_lexical_cast&)
        {
            assert (false);
            return false;
        }

        if (aclRuleValue == 0)
        {
            QPID_LOG(debug, "ACL: Comparison is always true when ACL rule value is zero");
            return true;
        }

        try
        {
            lookupValue = boost::lexical_cast<uint64_t>(theLookupValue);
        }
        catch(const boost::bad_lexical_cast&)
        {
            QPID_LOG(error,"ACL: Illegal value given in lookup for property '"
                << AclHelper::getPropertyStr(theProperty)
                << "' : " << theLookupValue);
            return false;
        }

        bool result =
        (theMaxFlag ? lookupValue > aclRuleValue : lookupValue < aclRuleValue);
        if ( result )
        {
            QPID_LOG(debug, "ACL: Limit exceeded for property '"
                << AclHelper::getPropertyStr(theProperty) << "'");
            return false;
        }

        return true;
    }

    const std::string DOMAIN_SEPARATOR("@");
    const std::string PERIOD(".");
    const std::string UNDERSCORE("_");
    //
    // substituteString
    //   Given a name string from an Acl rule, substitute the replacement into it
    //   wherever the placeholder directs.
    //
    void AclData::substituteString(std::string& targetString,
                                   const std::string& placeholder,
                                   const std::string& replacement)
    {
        assert (!placeholder.empty());
        if (placeholder.empty())
            return;
        size_t start_pos(0);
        while((start_pos = targetString.find(placeholder, start_pos)) != std::string::npos)
        {
            targetString.replace(start_pos, placeholder.length(), replacement);
            start_pos += replacement.length();
        }
    }


    //
    // normalizeUserId
    //   Given a name string return it in a form usable as topic keys:
    //     change "@" and "." to "_".
    //
    std::string AclData::normalizeUserId(const std::string& userId)
    {
        std::string normalId(userId);
        substituteString(normalId, DOMAIN_SEPARATOR, UNDERSCORE);
        substituteString(normalId, PERIOD,           UNDERSCORE);
        return normalId;
    }


    //
    // substituteUserId
    //   Given an Acl rule and an authenticated userId
    //   do the keyword substitutions on the rule.
    //
    void AclData::substituteUserId(std::string& ruleString,
                                   const std::string& userId)
    {
        size_t locDomSeparator(0);
        std::string user("");
        std::string domain("");
        std::string userdomain = normalizeUserId(userId);

        locDomSeparator = userId.find(DOMAIN_SEPARATOR);
        if (std::string::npos == locDomSeparator) {
            // "@" not found. There's just a user name
            user   = normalizeUserId(userId);
        } else {
            // "@" found, split the names. Domain may be blank.
            user   = normalizeUserId(userId.substr(0,locDomSeparator));
            domain = normalizeUserId(userId.substr(locDomSeparator+1));
        }

        substituteString(ruleString, ACL_KEYWORD_USER_SUBST,       user);
        substituteString(ruleString, ACL_KEYWORD_DOMAIN_SUBST,     domain);
        substituteString(ruleString, ACL_KEYWORD_USERDOMAIN_SUBST, userdomain);
    }


    //
    // substituteKeywords
    //   Given an Acl rule and an authenticated userId
    //   do reverse keyword substitutions on the rule.
    //   That is, replace the normalized name in the rule string with
    //   the keyword that represents it. This stragegy is used for
    //   topic key lookups where the keyword string proper is in the
    //   topic key search tree.
    //
    void AclData::substituteKeywords(std::string& ruleString,
                                     const std::string& userId)
    {
        size_t locDomSeparator(0);
        std::string user("");
        std::string domain("");
        std::string userdomain = normalizeUserId(userId);

        locDomSeparator = userId.find(DOMAIN_SEPARATOR);
        if (std::string::npos == locDomSeparator) {
            // "@" not found. There's just a user name
            user   = normalizeUserId(userId);
        } else {
            // "@" found, split the names
            user   = normalizeUserId(userId.substr(0,locDomSeparator));
            domain = normalizeUserId(userId.substr(locDomSeparator+1));
        }
        std::string oRule(ruleString);
        substituteString(ruleString, userdomain, ACL_KEYWORD_USERDOMAIN_SUBST);
        substituteString(ruleString, user,       ACL_KEYWORD_USER_SUBST);
        substituteString(ruleString, domain,     ACL_KEYWORD_DOMAIN_SUBST);
    }
}}
