/**
   * Get an array of all the collections that the current SWORD context will allow deposit onto in
   * the given DSpace context
   *
   * <p>The user may submit to a community if the following conditions are met:
   *
   * <p>IF: the authenticated user is an administrator AND: (the on-behalf-of user is an
   * administrator OR the on-behalf-of user is authorised to READ OR the on-behalf-of user is null)
   * OR IF: the authenticated user is authorised to READ AND: (the on-behalf-of user is an
   * administrator OR the on-behalf-of user is authorised to READ OR the on-behalf-of user is null)
   *
   * @param community
   * @return the array of allowed collections
   * @throws DSpaceSwordException
   */
  public List<Community> getCommunities(SwordContext swordContext, Community community)
      throws DSpaceSwordException {
    // a community is allowed if the following conditions are met
    //
    // - the authenticated user is an administrator
    // -- the on-behalf-of user is an administrator
    // -- the on-behalf-of user is authorised to READ
    // -- the on-behalf-of user is null
    // - the authenticated user is authorised to READ
    // -- the on-behalf-of user is an administrator
    // -- the on-behalf-of user is authorised to READ
    // -- the on-behalf-of user is null
    try {
      Community[] comms = community.getSubcommunities();
      List<Community> allowed = new ArrayList<Community>();

      for (int i = 0; i < comms.length; i++) {
        boolean authAllowed = false;
        boolean oboAllowed = false;

        // check for obo null
        if (swordContext.getOnBehalfOf() == null) {
          oboAllowed = true;
        }

        // look up the READ policy on the community.  This will include determining if the user is
        // an administrator
        // so we do not need to check that separately
        if (!authAllowed) {
          authAllowed =
              AuthorizeManager.authorizeActionBoolean(
                  swordContext.getAuthenticatorContext(), comms[i], Constants.READ);
        }

        // if we have not already determined that the obo user is ok to submit, look up the READ
        // policy on the
        // community.  THis will include determining if the user is an administrator.
        if (!oboAllowed) {
          oboAllowed =
              AuthorizeManager.authorizeActionBoolean(
                  swordContext.getOnBehalfOfContext(), comms[i], Constants.READ);
        }

        // final check to see if we are allowed to READ
        if (authAllowed && oboAllowed) {
          allowed.add(comms[i]);
        }
      }
      return allowed;

    } catch (SQLException e) {
      log.error("Caught exception: ", e);
      throw new DSpaceSwordException(e);
    }
  }
 /**
  * Is the given onBehalfOf user DSpace administrator? This translates as asking the question of
  * whether the given eperson is a member of the special DSpace group Administrator, with id 1
  *
  * @param swordContext
  * @return true if administrator, false if not
  * @throws java.sql.SQLException
  */
 public boolean isOnBehalfOfAdmin(SwordContext swordContext) throws DSpaceSwordException {
   EPerson onBehalfOf = swordContext.getOnBehalfOf();
   try {
     if (onBehalfOf != null) {
       return AuthorizeManager.isAdmin(swordContext.getOnBehalfOfContext());
     }
     return false;
   } catch (SQLException e) {
     log.error("Caught exception: ", e);
     throw new DSpaceSwordException(e);
   }
 }
 /**
  * Is the authenticated user a DSpace administrator? This translates as asking the question of
  * whether the given eperson is a member of the special DSpace group Administrator, with id 1
  *
  * @param swordContext
  * @return true if administrator, false if not
  * @throws java.sql.SQLException
  */
 public boolean isUserAdmin(SwordContext swordContext) throws DSpaceSwordException {
   try {
     EPerson authenticated = swordContext.getAuthenticated();
     if (authenticated != null) {
       return AuthorizeManager.isAdmin(swordContext.getAuthenticatorContext());
     }
     return false;
   } catch (SQLException e) {
     log.error("Caught exception: ", e);
     throw new DSpaceSwordException(e);
   }
 }
  /**
   * Get an array of all the collections that the current SWORD context will allow deposit onto in
   * the given DSpace context
   *
   * <p>IF: the authenticated user is an administrator AND: (the on-behalf-of user is an
   * administrator OR the on-behalf-of user is authorised to ADD OR the on-behalf-of user is null)
   * OR IF: the authenticated user is authorised to ADD AND: (the on-behalf-of user is an
   * administrator OR the on-behalf-of user is authorised to ADD OR the on-behalf-of user is null)
   *
   * @param swordContext
   * @return the array of allowed collections
   * @throws DSpaceSwordException
   */
  public List<org.dspace.content.Collection> getAllowedCollections(
      SwordContext swordContext, Community community) throws DSpaceSwordException {
    // a collection is allowed if the following conditions are met
    //
    // - the authenticated user is an administrator
    // -- the on-behalf-of user is an administrator
    // -- the on-behalf-of user is authorised to ADD
    // -- the on-behalf-of user is null
    // - the authenticated user is authorised to ADD
    // -- the on-behalf-of user is an administrator
    // -- the on-behalf-of user is authorised to ADD
    // -- the on-behalf-of user is null

    try {
      // get the context of the authenticated user
      Context authContext = swordContext.getAuthenticatorContext();

      // short cut by obtaining the collections to which the authenticated user can submit
      org.dspace.content.Collection[] cols =
          org.dspace.content.Collection.findAuthorized(authContext, community, Constants.ADD);
      List<org.dspace.content.Collection> allowed = new ArrayList<org.dspace.content.Collection>();

      // now find out if the obo user is allowed to submit to any of these collections
      for (int i = 0; i < cols.length; i++) {
        boolean oboAllowed = false;

        // check for obo null
        if (swordContext.getOnBehalfOf() == null) {
          oboAllowed = true;
        }

        // if we have not already determined that the obo user is ok to submit, look up the READ
        // policy on the
        // community.  THis will include determining if the user is an administrator.
        if (!oboAllowed) {
          oboAllowed =
              AuthorizeManager.authorizeActionBoolean(
                  swordContext.getOnBehalfOfContext(), cols[i], Constants.ADD);
        }

        // final check to see if we are allowed to READ
        if (oboAllowed) {
          allowed.add(cols[i]);
        }
      }
      return allowed;

    } catch (SQLException e) {
      log.error("Caught exception: ", e);
      throw new DSpaceSwordException(e);
    }
  }
  public boolean canSubmitTo(SwordContext swordContext, Item item) throws DSpaceSwordException {
    // a context can submit to an item if the following are satisfied
    //
    // 1/ the primary authenticating user is authenticated (which is implicit
    //      in there being a context in the first place)
    // 2/ If an On-Behalf-Of request, the On-Behalf-Of user is authorised to
    //      carry out the action and the authenticating user is in the list
    //      of allowed mediaters
    // 3/ If not an On-Behalf-Of request, the authenticating user is authorised
    //      to carry out the action

    try {
      boolean isObo = swordContext.getOnBehalfOf() != null;
      Context allowContext = null;
      if (isObo) {
        // we need to find out if the authenticated user is permitted to mediate
        if (!this.allowedToMediate(swordContext.getAuthenticatorContext())) {
          return false;
        }
        allowContext = swordContext.getOnBehalfOfContext();
      } else {
        allowContext = swordContext.getAuthenticatorContext();
      }

      // we now need to check whether the selected context that we are authorising
      // has the appropriate permissions
      boolean write = AuthorizeManager.authorizeActionBoolean(allowContext, item, Constants.WRITE);

      Bundle[] bundles = item.getBundles("ORIGINAL");
      boolean add = false;
      if (bundles.length == 0) {
        add = AuthorizeManager.authorizeActionBoolean(allowContext, item, Constants.ADD);
      } else {
        for (int i = 0; i < bundles.length; i++) {
          add = AuthorizeManager.authorizeActionBoolean(allowContext, bundles[i], Constants.ADD);
          if (!add) {
            break;
          }
        }
      }

      boolean allowed = write && add;
      return allowed;
    } catch (SQLException e) {
      log.error("Caught exception: ", e);
      throw new DSpaceSwordException(e);
    }
  }
  /**
   * Can the current SWORD Context permit deposit into the given collection in the given DSpace
   * Context
   *
   * <p>IF: the authenticated user is an administrator AND: (the on-behalf-of user is an
   * administrator OR the on-behalf-of user is authorised to ADD OR the on-behalf-of user is null)
   * OR IF: the authenticated user is authorised to ADD AND: (the on-behalf-of user is an
   * administrator OR the on-behalf-of user is authorised to ADD OR the on-behalf-of user is null)
   *
   * @param swordContext
   * @param collection
   * @return
   * @throws DSpaceSwordException
   */
  public boolean canSubmitTo(SwordContext swordContext, org.dspace.content.Collection collection)
      throws DSpaceSwordException {
    // a user can submit to a collection in the following conditions:
    //
    // - the authenticated user is an administrator
    // -- the on-behalf-of user is an administrator
    // -- the on-behalf-of user is authorised to ADD/in the submission group
    // -- the on-behalf-of user is null
    // - the authenticated user is authorised to ADD/in the submission group
    // -- the on-behalf-of user is an administrator
    // -- the on-behalf-of user is authorised to ADD/in the submission group
    // -- the on-behalf-of user is null

    try {
      boolean authAllowed = false;
      boolean oboAllowed = false;

      // check for obo null
      if (swordContext.getOnBehalfOf() == null) {
        oboAllowed = true;
      }

      // look up the READ policy on the collection.  This will include determining if the user is an
      // administrator
      // so we do not need to check that separately
      if (!authAllowed) {
        authAllowed =
            AuthorizeManager.authorizeActionBoolean(
                swordContext.getAuthenticatorContext(), collection, Constants.ADD);
      }

      // if we have not already determined that the obo user is ok to submit, look up the READ
      // policy on the
      // community.  THis will include determining if the user is an administrator.
      if (!oboAllowed) {
        oboAllowed =
            AuthorizeManager.authorizeActionBoolean(
                swordContext.getOnBehalfOfContext(), collection, Constants.ADD);
      }

      // final check to see if we are allowed to READ
      return (authAllowed && oboAllowed);

    } catch (SQLException e) {
      log.error("Caught exception: ", e);
      throw new DSpaceSwordException(e);
    }
  }
 /**
  * Is the onBehalfOf user a member of the given group or one of its sub groups
  *
  * @param group
  * @return
  */
 public boolean isOnBehalfOfInGroup(SwordContext swordContext, Group group) {
   EPerson onBehalfOf = swordContext.getOnBehalfOf();
   if (onBehalfOf != null) {
     return isInGroup(group, onBehalfOf);
   }
   return false;
 }
 /**
  * Is the authenticated user a member of the given group or one of its sub groups?
  *
  * @param group
  * @return
  */
 public boolean isUserInGroup(SwordContext swordContext, Group group) {
   EPerson authenticated = swordContext.getAuthenticated();
   if (authenticated != null) {
     return isInGroup(group, authenticated);
   }
   return false;
 }
  /**
   * Get a list of all the items that the current SWORD context will allow deposit onto in the given
   * DSpace context
   *
   * <p>IF: the authenticated user is an administrator AND: (the on-behalf-of user is an
   * administrator OR the on-behalf-of user is authorised to WRITE on the item and ADD on the
   * ORIGINAL bundle OR the on-behalf-of user is null) OR IF: the authenticated user is authorised
   * to WRITE on the item and ADD on the ORIGINAL bundle AND: (the on-behalf-of user is an
   * administrator OR the on-behalf-of user is authorised to WRITE on the item and ADD on the
   * ORIGINAL bundle OR the on-behalf-of user is null)
   *
   * @param swordContext
   * @return the array of allowed collections
   * @throws DSpaceSwordException
   */
  public List<Item> getAllowedItems(
      SwordContext swordContext, org.dspace.content.Collection collection)
      throws DSpaceSwordException {
    // an item is allowed if the following conditions are met
    //
    // - the authenticated user is an administrator
    // -- the on-behalf-of user is an administrator
    // -- the on-behalf-of user is authorised to WRITE on the item and ADD on the ORIGINAL bundle
    // -- the on-behalf-of user is null
    // - the authenticated user is authorised to WRITE on the item and ADD on the ORIGINAL bundle
    // -- the on-behalf-of user is an administrator
    // -- the on-behalf-of user is authorised to WRITE on the item and ADD on the ORIGINAL bundle
    // -- the on-behalf-of user is null

    try {
      List<Item> allowed = new ArrayList<Item>();
      ItemIterator ii = collection.getItems();

      while (ii.hasNext()) {
        Item item = ii.next();

        boolean authAllowed = false;
        boolean oboAllowed = false;

        // check for obo null
        if (swordContext.getOnBehalfOf() == null) {
          oboAllowed = true;
        }

        // get the "ORIGINAL" bundle(s)
        Bundle[] bundles = item.getBundles("ORIGINAL");

        // look up the READ policy on the community.  This will include determining if the user is
        // an administrator
        // so we do not need to check that separately
        if (!authAllowed) {
          boolean write =
              AuthorizeManager.authorizeActionBoolean(
                  swordContext.getAuthenticatorContext(), item, Constants.WRITE);

          boolean add = false;
          if (bundles.length == 0) {
            add =
                AuthorizeManager.authorizeActionBoolean(
                    swordContext.getAuthenticatorContext(), item, Constants.ADD);
          } else {
            for (int i = 0; i < bundles.length; i++) {
              add =
                  AuthorizeManager.authorizeActionBoolean(
                      swordContext.getAuthenticatorContext(), bundles[i], Constants.ADD);
              if (!add) {
                break;
              }
            }
          }

          authAllowed = write && add;
        }

        // if we have not already determined that the obo user is ok to submit, look up the READ
        // policy on the
        // community.  THis will include determining if the user is an administrator.
        if (!oboAllowed) {
          boolean write =
              AuthorizeManager.authorizeActionBoolean(
                  swordContext.getOnBehalfOfContext(), item, Constants.WRITE);

          boolean add = false;
          if (bundles.length == 0) {
            add =
                AuthorizeManager.authorizeActionBoolean(
                    swordContext.getAuthenticatorContext(), item, Constants.ADD);
          } else {
            for (int i = 0; i < bundles.length; i++) {
              add =
                  AuthorizeManager.authorizeActionBoolean(
                      swordContext.getAuthenticatorContext(), bundles[i], Constants.ADD);
              if (!add) {
                break;
              }
            }
          }

          oboAllowed = write && add;
        }

        // final check to see if we are allowed to READ
        if (authAllowed && oboAllowed) {
          allowed.add(item);
        }
      }

      return allowed;
    } catch (SQLException e) {
      throw new DSpaceSwordException(e);
    }
  }
  /**
   * Authenticate the given username/password pair, in conjunction with the onBehalfOf user. The
   * rules are that the username/password pair must successfully authenticate the user, and the
   * onBehalfOf user must exist in the user database.
   *
   * @param context
   * @param auth
   * @return a SWORD context holding the various user information
   * @throws SwordAuthException
   * @throws SwordError
   * @throws DSpaceSwordException
   */
  private SwordContext authenticate(Context context, AuthCredentials auth)
      throws SwordAuthException, SwordError, DSpaceSwordException {
    String obo = auth.getOnBehalfOf();
    String un = auth.getUsername();
    String pw = auth.getPassword();

    // smooth out the OnBehalfOf request, so that empty strings are
    // treated as null
    if ("".equals(obo)) {
      obo = null;
    }

    // first find out if we support on-behalf-of deposit
    boolean mediated =
        ConfigurationManager.getBooleanProperty("swordv2-server", "on-behalf-of.enable");
    if (!mediated && obo != null) {
      // user is trying to do a mediated deposit on a repository which does not support it
      log.error("Attempted mediated deposit on service not configured to do so");
      throw new SwordError(
          UriRegistry.ERROR_MEDIATION_NOT_ALLOWED,
          "Mediated deposit to this service is not permitted");
    }

    log.info(
        LogManager.getHeader(
            context, "sword_authenticate", "username=" + un + ",on_behalf_of=" + obo));

    try {
      // attempt to authenticate the primary user
      SwordContext sc = new SwordContext();
      EPerson ep = null;
      boolean authenticated = false;
      if (this.authenticates(context, un, pw)) {
        // if authenticated, obtain the eperson object
        ep = context.getCurrentUser();

        if (ep != null) {
          authenticated = true;
          sc.setAuthenticated(ep);
          // Set any special groups - invoke the authentication mgr.
          int[] groupIDs = AuthenticationManager.getSpecialGroups(context, null);

          for (int i = 0; i < groupIDs.length; i++) {
            context.setSpecialGroup(groupIDs[i]);
            log.debug("Adding Special Group id=" + String.valueOf(groupIDs[i]));
          }

          sc.setAuthenticatorContext(context);
          sc.setContext(context);
        }

        // if there is an onBehalfOfuser, then find their eperson
        // record, and if it exists set it.  If not, then the
        // authentication process fails
        EPerson epObo = null;
        if (obo != null) {
          epObo = EPerson.findByEmail(context, obo);
          if (epObo == null) {
            epObo = EPerson.findByNetid(context, obo);
          }

          if (epObo != null) {
            sc.setOnBehalfOf(epObo);
            Context oboContext = this.constructContext();
            oboContext.setCurrentUser(epObo);
            // Set any special groups - invoke the authentication mgr.
            int[] groupIDs = AuthenticationManager.getSpecialGroups(oboContext, null);

            for (int i = 0; i < groupIDs.length; i++) {
              oboContext.setSpecialGroup(groupIDs[i]);
              log.debug("Adding Special Group id=" + String.valueOf(groupIDs[i]));
            }
            sc.setContext(oboContext);
          } else {
            authenticated = false;
            throw new SwordError(
                UriRegistry.ERROR_TARGET_OWNER_UNKNOWN,
                "unable to identify on-behalf-of user: " + obo);
          }
        }
      }

      if (!authenticated) {
        // decide what kind of error to throw
        if (ep != null) {
          log.info(LogManager.getHeader(context, "sword_unable_to_set_user", "username=" + un));
          throw new SwordAuthException("Unable to authenticate with the supplied credentials");
        } else {
          // FIXME: this shouldn't ever happen now, but may as well leave it in just in case
          // there's a bug elsewhere
          log.info(
              LogManager.getHeader(
                  context,
                  "sword_unable_to_set_on_behalf_of",
                  "username=" + un + ",on_behalf_of=" + obo));
          throw new SwordAuthException("Unable to authenticate the onBehalfOf account");
        }
      }

      return sc;
    } catch (SQLException e) {
      log.error("caught exception: ", e);
      throw new DSpaceSwordException(
          "There was a problem accessing the repository user database", e);
    } catch (AuthorizeException e) {
      log.error("caught exception: ", e);
      throw new SwordAuthException("There was a problem authenticating or authorising the user", e);
    }
  }
  public Statement getStatement(
      String stateIRI,
      Map<String, String> accept,
      AuthCredentials authCredentials,
      SwordConfiguration swordConfig)
      throws SwordServerException, SwordError, SwordAuthException {
    SwordContext sc = null;
    try {
      SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;

      SwordAuthenticator auth = new SwordAuthenticator();
      sc = auth.authenticate(authCredentials);
      Context context = sc.getContext();

      if (log.isDebugEnabled()) {
        log.debug(LogManager.getHeader(context, "sword_get_statement", ""));
      }

      // log the request
      String un = authCredentials.getUsername() != null ? authCredentials.getUsername() : "NONE";
      String obo =
          authCredentials.getOnBehalfOf() != null ? authCredentials.getOnBehalfOf() : "NONE";
      log.info(
          LogManager.getHeader(
              context, "sword_get_statement", "username=" + un + ",on_behalf_of=" + obo));

      // first thing is to figure out what we're being asked to work on
      SwordUrlManager urlManager = config.getUrlManager(context, config);
      Item item = urlManager.getItem(context, stateIRI);
      if (item == null) {
        throw new SwordError(404);
      }

      // find out if we are allowed to read the item's statement
      AuthorizeManager.authorizeAction(context, item, Constants.READ);

      // find out, now we know what we're being asked for, whether this is allowed
      WorkflowManagerFactory.getInstance().retrieveStatement(context, item);

      String suffix = urlManager.getTypeSuffix(context, stateIRI);
      SwordStatementDisseminator disseminator = null;

      if (suffix != null) {
        Map<Float, List<String>> analysed = new HashMap<Float, List<String>>();
        List<String> list = new ArrayList<String>();
        list.add(suffix);
        analysed.put((float) 1.0, list);
        disseminator = SwordDisseminatorFactory.getStatementInstance(analysed);
      } else {
        // we rely on the content negotiation to do the work
        String acceptContentType = this.getHeader(accept, "Accept", null);

        // we extract from the Accept header the ordered list of content types
        TreeMap<Float, List<String>> analysed = this.analyseAccept(acceptContentType);

        // the meat of this is done by the package disseminator
        disseminator = SwordDisseminatorFactory.getStatementInstance(analysed);
      }

      Statement statement = disseminator.disseminate(context, item);
      return statement;
    } catch (AuthorizeException e) {
      throw new SwordAuthException();
    } catch (SQLException e) {
      throw new SwordServerException(e);
    } catch (DSpaceSwordException e) {
      throw new SwordServerException(e);
    } finally {
      if (sc != null) {
        sc.abort();
      }
    }
  }