/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 */

package org.opends.server.controls;
import org.opends.messages.Message;

import org.opends.server.types.*;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.ldap.LDAPResultCode;
import static org.opends.server.util.ServerConstants.OID_GET_EFFECTIVE_RIGHTS;
import static org.opends.server.util.Validator.ensureNotNull;
import static org.opends.server.util.StaticUtils.toLowerCase;
import org.opends.server.core.DirectoryServer;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;

/**
 * This class partially implements the geteffectiverights control as defined
 * in draft-ietf-ldapext-acl-model-08.txt. The main differences are:
 *
 *  - The response control is not supported. Instead the dseecompat
 *    geteffectiverights control implementation creates attributes containing
 *    right information strings and adds those attributes to the
 *    entry being returned. The attribute type names are dynamically created;
 *    see the dseecompat's AciGetEffectiveRights class for details.
 *
 *  - The dseecompat implementation allows additional attribute types
 *    in the request control for which rights information can be returned.
 *    These are known as the specified attribute types.
 *
 * The dseecompat request control value is the following:
 *
 * <BR>
 * <PRE>
 *  GetRightsControl ::= SEQUENCE {
 *    authzId    authzId
 *    attributes  SEQUENCE OF AttributeType
 *  }
 *
 *   -- Only the "dn:DN form is supported.
 *
 * </PRE>
 *
 **/
public class GetEffectiveRights extends Control {
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();


  //The DN representing the authzId. May be null.
  private DN authzDN=null;

  //The list of additional attribute types to return rights for. May be null.
  private List<AttributeType> attrs=null;

  /**
   *  Create a new geteffectiverights control with null authzDN and null
   *  attribute list.
   */
  public GetEffectiveRights() {
    super(OID_GET_EFFECTIVE_RIGHTS, true, null);
  }

  /**
   * Create a new geteffectiverights control with the specified raw octet
   * string, an authzDN and an attribute list.
   *
   * @param val  The octet string repsentation of the control value.
   *
   * @param authzDN  The authzDN.
   *
   * @param attrs  The list of additional attributes to be returned.
   */
  public GetEffectiveRights(ASN1OctetString val, DN authzDN,
                            List<AttributeType> attrs) {
    super(OID_GET_EFFECTIVE_RIGHTS, true, val);
    this.authzDN=authzDN;
    this.attrs=attrs;
  }

  /**
   * Return the authzDN parsed from the control.
   *
   * @return The DN representing the authzId.
   */
  public DN getAuthzDN () {
    return authzDN;
  }

  /**
   * Return the requested additional attributes parsed from the control. Known
   * as the specified attributes.
   *
   * @return  The list containing any additional attributes to return rights
   *          about.
   */
  public List<AttributeType> getAttributes() {
    return attrs;
  }


  /**
   * Decodes the provided ASN1 element.  Assume that it is a ASN1 sequence
   * of attributetypes.
   *
   * @param attributeElement   The ASN1 element to be decoded.
   *
   * @return  A list of attribute types to process rights of.
   *
   * @throws ASN1Exception If the attribute element cannot be decoded as a
   *                       sequence.
   */
  private static
  List<AttributeType> decodeAttributeSequence(ASN1Element attributeElement)
          throws ASN1Exception {
    List<AttributeType>  attributeList = new LinkedList<AttributeType>();
    //Decode the sequence element and put the individual elements in array.
    ArrayList<ASN1Element> attrElems =
            attributeElement.decodeAsSequence().elements();
    int numAttrElements = attrElems.size();
    for(int i=0; i < numAttrElements; i++) {
      //Decode as an octet string.
      ASN1OctetString tmp=attrElems.get(i).decodeAsOctetString();
      //Get an attribute type for it and add to the list.
      AttributeType attributeType;
      if((attributeType =
              DirectoryServer.getAttributeType(tmp.toString())) == null)
        attributeType =
                DirectoryServer.getDefaultAttributeType(tmp.toString());
      attributeList.add(attributeType);
    }
    return attributeList;
  }

  /**
   * Decodes the request control's value octet string into a GetEffectiveRights
   * class. It assumes that it is an ASN1 sequence as described in class
   * description.
   *
   * @param val The octet string repsentation of the control value.
   *
   * @return  A decoded GetEffectiveRights class representing the request
   *          control.
   *
   * @throws LDAPException   If the request control's value contains errors
   *                         causing a valid GetEffectiveRights class to not
   *                         be created.
   */
  private static
  GetEffectiveRights decodeValueSequence(ASN1OctetString val )
          throws LDAPException {
    DN authzDN;
    List<AttributeType> attrs=null;
    String authzIDString="";
    try {
      ASN1Element sequence = ASN1Element.decode(val.value());
      ArrayList<ASN1Element> elements =
              sequence.decodeAsSequence().elements();
      ASN1OctetString authzID = elements.get(0).decodeAsOctetString();
      //There is an sequence containing an attribute list, try to decode it.
      if(elements.size() == 2)
        attrs=decodeAttributeSequence(elements.get(1));
      authzIDString = authzID.stringValue();
      String lowerAuthzIDString = toLowerCase(authzIDString);
      //Make sure authzId starts with "dn:" and is a valid DN.
      if (lowerAuthzIDString.startsWith("dn:"))
         authzDN = DN.decode(authzIDString.substring(3));
      else {
         Message message = INFO_GETEFFECTIVERIGHTS_INVALID_AUTHZID.get(
                 String.valueOf(authzID));
         throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
      }
    } catch (ASN1Exception e) {
         if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
         }

         Message message =
             INFO_GETEFFECTIVERIGHTS_DECODE_ERROR.get(e.getMessage());
         throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
    } catch (DirectoryException de) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }

        Message message = INFO_CANNOT_DECODE_GETEFFECTIVERIGHTS_AUTHZID_DN.get(
            authzIDString.substring(3), de.getMessageObject());
        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
    }
    return new GetEffectiveRights(val, authzDN, attrs);
  }

  /**
   * Decodes the request control's value into a GetEffectiveRights class.
   *
   * @param control  The control class representing the request control.
   *
   * @return  A decoded GetEffectiveRights class representing the request
   *          control.
   *
   * @throws LDAPException   If the request control's value contains errors
   *                         causing a valid GetEffectiveRights class to not
   *                         be created.
   */
  public static
  GetEffectiveRights decodeControl(Control control) throws LDAPException {
    ensureNotNull(control);
    ASN1OctetString value=control.getValue();
    //If the value is null create a GetEffectiveRights class with null
    //authzDN and attribute list, else try to decode the value.
    if(value == null)
      return new GetEffectiveRights();
    else
      return GetEffectiveRights.decodeValueSequence(value);
  }
}
