Package com.google.enterprise.connector.notes

Source Code of com.google.enterprise.connector.notes.NotesAuthorizationManager

// Copyright 2011 Google Inc.
//
// 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 com.google.enterprise.connector.notes;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.enterprise.connector.notes.NotesUserGroupManager.User;
import com.google.enterprise.connector.notes.client.NotesDatabase;
import com.google.enterprise.connector.notes.client.NotesDocument;
import com.google.enterprise.connector.notes.client.NotesSession;
import com.google.enterprise.connector.notes.client.NotesView;
import com.google.enterprise.connector.notes.client.NotesViewNavigator;
import com.google.enterprise.connector.spi.AuthenticationIdentity;
import com.google.enterprise.connector.spi.AuthorizationManager;
import com.google.enterprise.connector.spi.AuthorizationResponse;
import com.google.enterprise.connector.spi.RepositoryException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

class NotesAuthorizationManager implements AuthorizationManager {
  private static final String CLASS_NAME =
      NotesAuthorizationManager.class.getName();
  private static final Logger LOGGER = Logger.getLogger(CLASS_NAME);

  private final NotesConnectorSession ncs;

  public NotesAuthorizationManager(NotesConnectorSession session) {
    final String METHOD = "NotesAuthorizationManager";
    LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
        "NotesAuthorizationManager being created.");
    ncs = session;
  }

  /* The docid is generated by the application and always takes the format
   * http://server.domain/ReplicaID/0/UniversalDocumentID
   * The protocol is always http://
   *
   * TODO: We need better documentation for the expected docid
   * format here. Plus constants (preferably generated
   * ones). Also, what happens if the docid uses HTTPS?
   *
   * Consider using java.net.URL and String.split to get the
   * pieces. Attachment docid values can be longer.
   */
  protected String getRepIdFromDocId(String docId) {
    int start = docId.indexOf('/', 7)// Find the first slash after http://
    return docId.substring(start + 1, start + 17);
  }

  /* The docid is generated by the application and always takes the format
   * http://server.domain/ReplicaID/0/UniversalDocumentID
   * The protocol is always http://
   */
  protected String getUNIDFromDocId(String docId) {
    int start = docId.indexOf('/', 7)// Find the first slash after http://
    return docId.substring(start + 20, start + 52);
  }

  // Explain Lotus Notes Authorization Rules

  // TODO: Add LRU Cache for ALLOW/DENY
  @Override
  @SuppressWarnings("unchecked")
  public Collection<AuthorizationResponse> authorizeDocids(
      Collection<String> docIds, AuthenticationIdentity id) {
    final String METHOD = "authorizeDocids";
    long elapsedTimeMillis = 0;
    long startTime = System.currentTimeMillis();

    ArrayList<AuthorizationResponse> authorized =
        new ArrayList<AuthorizationResponse>(docIds.size());
    try {
      // Find the user in the connector cache.
      String gsaName = ncs.getUsernameType().getUsername(id);
      User user = ncs.getUserGroupManager().getUserByGsaName(gsaName);
      if (user == null) {
        LOGGER.logp(Level.FINE, CLASS_NAME, METHOD,
            "Person not found in connector user database: " + gsaName +
            " using " + ncs.getUsernameType() + " username type");
        for (String docId : docIds) {
          authorized.add(new AuthorizationResponse(false, docId));
        }
      } else {
        LOGGER.logp(Level.FINE, CLASS_NAME, METHOD,
            "Authorizing documents for user " + gsaName +
            " using " + ncs.getUsernameType() + " username type");
        ArrayList<String> userGroups = new ArrayList<String>(user.getGroups());
        LOGGER.logp(Level.FINE, CLASS_NAME, METHOD,
            "Groups for " + gsaName + " are: " + userGroups);

        NotesSession ns = null;
        try {
          ns = ncs.createNotesSession();
          NotesDatabase cdb =
              ns.getDatabase(ncs.getServer(), ncs.getDatabase());
          NotesView securityView = cdb.getView(NCCONST.VIEWSECURITY);
          for (String docId : docIds) {
            NotesViewNavigator secVN = null;
            NotesDocument dbdoc = null;
            try {
              // Extract the database and UNID from the URL
              String repId = getRepIdFromDocId(docId);
              String unid = getUNIDFromDocId(docId);
              LOGGER.logp(Level.FINER, CLASS_NAME, METHOD,
                  "Authorizing document: " + repId + " : " + unid);

              // Get the category from the security view for this
              // database. The first document in the category is
              // ALWAYS the database document.
              secVN = securityView.createViewNavFromCategory(repId);
              dbdoc = secVN.getFirstDocument().getDocument();
              boolean dballow =
                  checkDatabaseAccess(dbdoc, user);

              // Only check document level security if we are
              // allowed at the database level. Assume we have
              // access to the document unless proven
              // otherwise...
              boolean docallow = true;
              if (dballow) {
                Collection<String> readers =
                    ncs.getNotesDocumentManager()
                        .getDocumentReaders(unid, repId);
                if (readers.size() > 0) {
                  docallow = checkDocumentReaders(user, readers, repId);
                } else {
                  LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
                      "No document level security for " + unid);
                }
              }
              boolean allow = docallow && dballow;
              LOGGER.logp(Level.FINER, CLASS_NAME, METHOD,
                  "Final auth decision is " + allow + " " + unid);
              authorized.add(new AuthorizationResponse(allow, docId));
            } catch (Throwable t) {
              LOGGER.logp(Level.WARNING, CLASS_NAME, METHOD,
                  "Failed to complete check for: " + docId, t);
              authorized.add(new AuthorizationResponse(
                      AuthorizationResponse.Status.INDETERMINATE, docId));
            } finally {
              Util.recycle(dbdoc);
              Util.recycle(secVN);
              // Log timing for each document.
              if (LOGGER.isLoggable(Level.FINER)) {
                elapsedTimeMillis = System.currentTimeMillis() - startTime;
                LOGGER.logp(Level.FINER, CLASS_NAME, METHOD,
                    "ElapsedAuthorizationResponseTime: " + elapsedTimeMillis
                    + " Documents authorized: " + authorized.size());
              }
            }
          }
        } finally {
          ncs.closeNotesSession(ns);
        }
      }
    } catch (Exception e) {
      LOGGER.log(Level.SEVERE, CLASS_NAME, e);
    }

    if (LOGGER.isLoggable(Level.FINER)) {
      for (int i = 0; i < authorized.size(); i++) {
        AuthorizationResponse ar = authorized.get(i);
        LOGGER.logp(Level.FINER, CLASS_NAME, METHOD,
            "AuthorizationResponse: " + ar.getDocid() + " : " + ar.isValid());
      }
    }
    // Get elapsed time in milliseconds
    elapsedTimeMillis = System.currentTimeMillis() - startTime;
    LOGGER.logp(Level.FINE, CLASS_NAME, METHOD,
        "TotalAuthorizationResponseTime: " + elapsedTimeMillis
        + " milliseconds.  Documents in batch: " + docIds.size() +
        " Documents authorized: " + authorized.size());
    return authorized;
  }

  protected static String getCommonName(String notesName) {
    if (notesName.startsWith("cn=")) {
      int index = notesName.indexOf('/');
      if (index > 0)
        return notesName.substring(3, index);
    }
    return null;
  }

  /* getDocumentReaders lower-cases the readers list. */
  @VisibleForTesting
  boolean checkDocumentReaders(User user,
      Collection<String> readers, String repId) throws RepositoryException {
    final String METHOD = "checkDocumentReaders";
    LOGGER.entering(CLASS_NAME, METHOD);

    LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
        "Document reader list is " + readers);
    try {
      // Check using the Notes name
      if (readers.contains(user.getNotesName())) {
        LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
            "ALLOWED: User is in document readers" + user.getNotesName());
        return true;
      }

      // Check using the common name
      String commonName = getCommonName(user.getNotesName());
      if (readers.contains(commonName)) {
          LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
              "ALLOWED: User is in document readers " + commonName);
          return true;
      }

      // Check using groups
      for (String group : user.getGroups()) {
        if (readers.contains(group)) {
          LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
              "ALLOWED: Group is in document readers " + group);
          return true;
        }
      }

      // Check using roles
      // Testing with R8.5 roles do not expand to nested groups.
      // You must be a direct member of the group to get the role.
      // TODO: Check and validate this with other versions
      // TODO: If this is true, the UserGroupManager will need to
      // distinguish between direct and indirect group
      // membership.
      for (String role : user.getRolesByDatabase(repId)) {
        if (readers.contains(role)) {
          LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
              "ALLOWED: Role is in document readers " + role);
          return true;
        }
      }

      LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
          "DENIED: User's security principals are not in "
          + "document access lists.");
      return false;
    } finally {
      LOGGER.exiting(CLASS_NAME, METHOD);
    }
  }

  @VisibleForTesting
  boolean checkDatabaseAccess(NotesDocument dbDoc, User user)
      throws RepositoryException {
    final String METHOD = "checkDatabaseAccess";
    LOGGER.entering(CLASS_NAME, METHOD);

    try {
      String commonName = getCommonName(user.getNotesName());
      if (checkDenyUser(dbDoc, user.getNotesName(), commonName)) {
        return false;
      }
      // TODO: why don't we check for deny-by-group?
      if (checkAllowUser(dbDoc, user.getNotesName(), commonName)) {
        return true;
      }
      if (checkAllowGroup(dbDoc, user.getGroups())) {
        return true;
      }
    } finally {
      LOGGER.exiting(CLASS_NAME, METHOD);
    }
    return false;
  }

  // TODO: the access groups may not need to be summary data. to avoid 64k
  @VisibleForTesting
  boolean checkAllowGroup(NotesDocument dbdoc, Collection<String> userGroups)
      throws RepositoryException {
    final String METHOD = "checkAllowGroup";
    LOGGER.entering(CLASS_NAME, METHOD);

    try {
      ArrayList<String> allowGroups =
          toLowerCase(dbdoc.getItemValue(NCCONST.NCITM_DBPERMITGROUPS));
      LOGGER.logp(Level.FINER, CLASS_NAME, METHOD,
          "Allow groups are: " + allowGroups.toString());

      for (String group: userGroups) {
        if (allowGroups.contains(group)) {
          LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
              "ALLOWED: User is allowed through group " + group);
          return true;
        }
      }
      return false;
    } finally {
      LOGGER.exiting(CLASS_NAME, METHOD);
    }
  }

  @VisibleForTesting
  boolean checkAllowUser(NotesDocument dbdoc, String... userNames)
      throws RepositoryException {
    final String METHOD = "checkAllowUser";
    LOGGER.entering(CLASS_NAME, METHOD);

    try {
      ArrayList<String> allowList =
          toLowerCase(dbdoc.getItemValue(NCCONST.NCITM_DBPERMITUSERS));
      boolean result = false;
      if (allowList.contains("-default-")) {
        LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
            "ALLOWED: -Default- is allowed");
        result = true;
      } else {
        for (String userName : userNames) {
          if (allowList.contains(userName)) {
            LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
                "ALLOWED: User is explictly allowed " + userName);
            result = true;
            break;
          }
        }
      }
      return result;
    } finally {
      LOGGER.exiting(CLASS_NAME, METHOD);
    }
  }

  @VisibleForTesting
  boolean checkDenyUser(NotesDocument dbdoc, String... userNames)
      throws RepositoryException {
    final String METHOD = "checkDenyUser";
    LOGGER.entering(CLASS_NAME, METHOD);
    try {
      ArrayList<String> denyList =
          toLowerCase(dbdoc.getItemValue(NCCONST.NCITM_DBNOACCESSUSERS));
      for (String userName : userNames) {
        if (denyList.contains(userName)) {
          LOGGER.logp(Level.FINEST, CLASS_NAME, METHOD,
              "DENIED: User is explictly denied " + userName);
          return true;
        }
      }
      return false;
    } finally {
      LOGGER.exiting(CLASS_NAME, METHOD);
    }
  }

  private ArrayList<String> toLowerCase(Vector<?> items) {
    ArrayList<String> lcList = Lists.newArrayList();
    for (Object item : items) {
      lcList.add(item.toString().toLowerCase());
    }
    return lcList;
  }
}
TOP

Related Classes of com.google.enterprise.connector.notes.NotesAuthorizationManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.