All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.orientechnologies.security.auditing.OAuditingHook Maven / Gradle / Ivy

/**
 * Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *
 * 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.
 *
 * For more information: http://www.orientdb.com
 */
package com.orientechnologies.security.auditing;

import com.orientechnologies.common.parser.OVariableParser;
import com.orientechnologies.common.parser.OVariableParserListener;
import com.orientechnologies.orient.core.command.OCommandExecutor;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.db.ODatabase;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseListener;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.hook.ORecordHookAbstract;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.metadata.security.OSecurityUser;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.security.OAuditingOperation;
import com.orientechnologies.orient.server.OServer;
import com.orientechnologies.orient.server.OSystemDatabase;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Hook to audit database access.
 *
 * @author Luca Garulli
 */
public class OAuditingHook extends ORecordHookAbstract implements ODatabaseListener {
  private final Map classes                    = new HashMap(20);
  private final OAuditingLoggingThread            auditingThread;

  private Map> operations                 = new ConcurrentHashMap>();
  private volatile LinkedBlockingQueue auditingQueue;
  private Set             commands                   = new HashSet();
  private boolean                                 onGlobalCreate;
  private boolean                                 onGlobalRead;
  private boolean                                 onGlobalUpdate;
  private boolean                                 onGlobalDelete;
  private OAuditingClassConfig                    defaultConfig              = new OAuditingClassConfig();
  private OAuditingSchemaConfig                   schemaConfig;
  private ODocument                               iConfiguration;

  private static class OAuditingCommandConfig {
    public String regex;
    public String message;

    public OAuditingCommandConfig(final ODocument cfg) {
      regex = cfg.field("regex");
      message = cfg.field("message");
    }
  }

  private static class OAuditingClassConfig {
    public boolean polymorphic          = true;
    public boolean onCreateEnabled      = false;
    public String  onCreateMessage;
    public boolean onReadEnabled        = false;
    public String  onReadMessage;
    public boolean onUpdateEnabled      = false;
    public String  onUpdateMessage;
    public boolean onUpdateChanges      = true;
    public boolean onDeleteEnabled      = false;
    public String  onDeleteMessage;

    public OAuditingClassConfig() {
    }

    public OAuditingClassConfig(final ODocument cfg) {
      if (cfg.containsField("polymorphic"))
        polymorphic = cfg.field("polymorphic");

      // CREATE
      if (cfg.containsField("onCreateEnabled"))
        onCreateEnabled = cfg.field("onCreateEnabled");
      if (cfg.containsField("onCreateMessage"))
        onCreateMessage = cfg.field("onCreateMessage");

      // READ
      if (cfg.containsField("onReadEnabled"))
        onReadEnabled = cfg.field("onReadEnabled");
      if (cfg.containsField("onReadMessage"))
        onReadMessage = cfg.field("onReadMessage");

      // UPDATE
      if (cfg.containsField("onUpdateEnabled"))
        onUpdateEnabled = cfg.field("onUpdateEnabled");
      if (cfg.containsField("onUpdateMessage"))
        onUpdateMessage = cfg.field("onUpdateMessage");
      if (cfg.containsField("onUpdateChanges"))
        onUpdateChanges = cfg.field("onUpdateChanges");

      // DELETE
      if (cfg.containsField("onDeleteEnabled"))
        onDeleteEnabled = cfg.field("onDeleteEnabled");
      if (cfg.containsField("onDeleteMessage"))
        onDeleteMessage = cfg.field("onDeleteMessage");
    }
  }

  // Handles the auditing-config "schema" configuration.
  private class OAuditingSchemaConfig extends OAuditingConfig {
    private boolean onCreateClassEnabled = false;
    private String onCreateClassMessage;

    private boolean onDropClassEnabled = false;
    private String onDropClassMessage;

    public OAuditingSchemaConfig(final ODocument cfg) {
      if (cfg.containsField("onCreateClassEnabled"))
        onCreateClassEnabled = cfg.field("onCreateClassEnabled");
        
      onCreateClassMessage = cfg.field("onCreateClassMessage");
      
      if (cfg.containsField("onDropClassEnabled"))
        onDropClassEnabled = cfg.field("onDropClassEnabled");
        
      onDropClassMessage = cfg.field("onDropClassMessage");
    }
    
    @Override
    public String formatMessage(final OAuditingOperation op, final String subject) {
      if (op == OAuditingOperation.CREATEDCLASS) {
      	return resolveMessage(onCreateClassMessage, "class", subject);
      } else
      if (op == OAuditingOperation.DROPPEDCLASS) {
      	return resolveMessage(onDropClassMessage, "class", subject);
      }
      
      return subject;
    }

    @Override
    public boolean isEnabled(OAuditingOperation op) {
      if (op == OAuditingOperation.CREATEDCLASS) {
      	return onCreateClassEnabled;
      } else
      if (op == OAuditingOperation.DROPPEDCLASS) {
      	return onDropClassEnabled;
      }

    	return false;
    }    
  }


  //// OAuditingHook
  public OAuditingHook(final String iConfiguration) {
    this(new ODocument().fromJSON(iConfiguration, "noMap"), null);
  }

  public OAuditingHook(final String iConfiguration, final OServer server) {
    this(new ODocument().fromJSON(iConfiguration, "noMap"), server);
  }

  public OAuditingHook(final ODocument iConfiguration) {
    this(iConfiguration, null);
  }

  public OAuditingHook(final ODocument iConfiguration, final OServer server) {
    this.iConfiguration = iConfiguration;

    onGlobalCreate = onGlobalRead = onGlobalUpdate = onGlobalDelete = false;

    final ODocument classesCfg = iConfiguration.field("classes");
    if (classesCfg != null) {
      for (String c : classesCfg.fieldNames()) {
        final OAuditingClassConfig cfg = new OAuditingClassConfig((ODocument) classesCfg.field(c));
        if (c.equals("*"))
          defaultConfig = cfg;
        else
          classes.put(c, cfg);

        if (cfg.onCreateEnabled)
          onGlobalCreate = true;
        if (cfg.onReadEnabled)
          onGlobalRead = true;
        if (cfg.onUpdateEnabled)
          onGlobalUpdate = true;
        if (cfg.onDeleteEnabled)
          onGlobalDelete = true;
      }
    }

    final Iterable commandCfg = iConfiguration.field("commands");

    if (commandCfg != null) {

      for (ODocument cfg : commandCfg) {
        commands.add(new OAuditingCommandConfig(cfg));
      }
    }

    final ODocument schemaCfgDoc = iConfiguration.field("schema");
    if (schemaCfgDoc != null) {    	
    	schemaConfig = new OAuditingSchemaConfig(schemaCfgDoc);
    }

    auditingQueue = new LinkedBlockingQueue();
    auditingThread = new OAuditingLoggingThread(ODatabaseRecordThreadLocal.instance().get().getName(), auditingQueue, server);

    auditingThread.start();
  }

  public OAuditingHook(final OServer server) {
    auditingQueue = new LinkedBlockingQueue();
    auditingThread = new OAuditingLoggingThread(OSystemDatabase.SYSTEM_DB_NAME, auditingQueue, server);

    auditingThread.start();
  }

  @Override
  public void onCreate(ODatabase iDatabase) {
  }

  @Override
  public void onDelete(ODatabase iDatabase) {
  }

  @Override
  public void onOpen(ODatabase iDatabase) {
  }

  @Override
  public void onBeforeTxBegin(ODatabase iDatabase) {
  }

  @Override
  public void onBeforeTxRollback(ODatabase iDatabase) {
  }

  @Override
  public void onAfterTxRollback(ODatabase iDatabase) {

    synchronized (operations) {
      operations.remove(iDatabase);
    }
  }

  @Override
  public void onBeforeTxCommit(ODatabase iDatabase) {

  }

  @Override
  public void onAfterTxCommit(ODatabase iDatabase) {

    List oDocuments = null;

    synchronized (operations) {
      oDocuments = operations.remove(iDatabase);
    }
    if (oDocuments != null) {
      for (ODocument oDocument : oDocuments) {
        auditingQueue.offer(oDocument);
      }
    }
  }

  @Override
  public void onClose(ODatabase iDatabase) {
  }

  @Override
  public void onBeforeCommand(OCommandRequestText iCommand, OCommandExecutor executor) {
  }

  @Override
  public void onAfterCommand(OCommandRequestText iCommand, OCommandExecutor executor, Object result) {
    logCommand(iCommand.getText());
  }

  @Override
  public boolean onCorruptionRepairDatabase(ODatabase iDatabase, String iReason, String iWhatWillbeFixed) {
    return false;
  }

  public ODocument getConfiguration() {
    return iConfiguration;
  }

  @Override
  public void onRecordAfterCreate(final ORecord iRecord) {
    if (!onGlobalCreate)
      return;

    log(OAuditingOperation.CREATED, iRecord);
  }

  @Override
  public void onRecordAfterRead(final ORecord iRecord) {
    if (!onGlobalRead)
      return;

    log(OAuditingOperation.LOADED, iRecord);
  }

  @Override
  public void onRecordAfterUpdate(final ORecord iRecord) {
    if (!onGlobalUpdate)
      return;

    log(OAuditingOperation.UPDATED, iRecord);
  }

  @Override
  public void onRecordAfterDelete(final ORecord iRecord) {
    if (!onGlobalDelete)
      return;

    log(OAuditingOperation.DELETED, iRecord);
  }

  @Override
  public DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
    return DISTRIBUTED_EXECUTION_MODE.SOURCE_NODE;
  }

  protected void logCommand(final String command) {
    if(auditingQueue == null) return;

    for (OAuditingCommandConfig cfg : commands) {
      if (command.matches(cfg.regex)) {
        final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.instance().get();
        
        String username = null;

        final OSecurityUser user = db.getUser();
        if (user != null)
          username = user.getName();
        
        final ODocument doc = createLogDocument(OAuditingOperation.COMMAND, db.getName(), username, formatCommandNote(command, cfg.message));
        auditingQueue.offer(doc);
      }
    }
  }

  private String formatCommandNote(final String command, String message) {
    if (message == null || message.isEmpty())
      return command;
    return (String) OVariableParser.resolveVariables(message, "${", "}", new OVariableParserListener() {
      @Override
      public Object resolve(final String iVariable) {
        if (iVariable.startsWith("command")) {
          return command;
        }
        return null;
      }
    });
  }

  protected void log(final OAuditingOperation operation, final ORecord iRecord) {
    if (auditingQueue == null)
      // LOGGING THREAD INACTIVE, SKIP THE LOG
      return;

    final OAuditingClassConfig cfg = getAuditConfiguration(iRecord);
    if (cfg == null)
      // SKIP
      return;

    ODocument changes = null;
    String note = null;

    switch (operation) {
    case CREATED:
      if (!cfg.onCreateEnabled)
        // SKIP
        return;
      note = cfg.onCreateMessage;
      break;
    case LOADED:
      if (!cfg.onReadEnabled)
        // SKIP
        return;
      note = cfg.onReadMessage;
      break;
    case UPDATED:
      if (!cfg.onUpdateEnabled)
        // SKIP
        return;
      note = cfg.onUpdateMessage;

      if (iRecord instanceof ODocument && cfg.onUpdateChanges) {
        final ODocument doc = (ODocument) iRecord;
        changes = new ODocument();

        for (String f : doc.getDirtyFields()) {
          ODocument fieldChanges = new ODocument();
          fieldChanges.field("from", doc.getOriginalValue(f));
          fieldChanges.field("to", (Object) doc.rawField(f));
          changes.field(f, fieldChanges, OType.EMBEDDED);
        }
      }
      break;
    case DELETED:
      if (!cfg.onDeleteEnabled)
        // SKIP
        return;
      note = cfg.onDeleteMessage;
      break;

    }

    final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.instance().get();

    String username = null;

    final OSecurityUser user = db.getUser();
    if (user != null)
      username = user.getName();

    final ODocument doc = createLogDocument(operation, db.getName(), username, formatNote(iRecord, note));
    doc.field("record", iRecord.getIdentity());
    if (changes != null)
      doc.field("changes", changes, OType.EMBEDDED);

    if (db.getTransaction().isActive()) {
      synchronized (operations) {
        List oDocuments = operations.get(db);
        if (oDocuments == null) {
          oDocuments = new ArrayList();
          operations.put(db, oDocuments);
        }
        oDocuments.add(doc);
      }
    } else {
      auditingQueue.offer(doc);
    }
  }

  private String formatNote(final ORecord iRecord, final String iNote) {
    if (iNote == null)
      return null;

    return (String) OVariableParser.resolveVariables(iNote, "${", "}", new OVariableParserListener() {
      @Override
      public Object resolve(final String iVariable) {
        if (iVariable.startsWith("field.")) {
          if (iRecord instanceof ODocument) {
            final String fieldName = iVariable.substring("field.".length());
            return ((ODocument) iRecord).field(fieldName);
          }
        }
        return null;
      }
    });
  }

  private OAuditingClassConfig getAuditConfiguration(final ORecord iRecord) {
    OAuditingClassConfig cfg = null;

    if (iRecord instanceof ODocument) {
      OClass cls = ((ODocument) iRecord).getSchemaClass();
      if (cls != null) {

        if (cls.getName().equals(ODefaultAuditing.AUDITING_LOG_CLASSNAME))
          // SKIP LOG CLASS
          return null;

        cfg = classes.get(cls.getName());

        // BROWSE SUPER CLASSES UP TO ROOT
        while (cfg == null && cls != null) {
          cls = cls.getSuperClass();
          if (cls != null) {
            cfg = classes.get(cls.getName());
            if (cfg != null && !cfg.polymorphic) {
              // NOT POLYMORPHIC: IGNORE IT AND EXIT FROM THE LOOP
              cfg = null;
              break;
            }
          }
        }

      }
    }

    if (cfg == null)
      // ASSIGN DEFAULT CFG (*)
      cfg = defaultConfig;

    return cfg;
  }

  public void shutdown(final boolean waitForAllLogs) {
    if (auditingThread != null) {
      auditingThread.sendShutdown(waitForAllLogs);
      auditingQueue = null;
    }
  }

/*
  private OAuditingClassConfig getAuditConfiguration(OClass cls) {
    OAuditingClassConfig cfg = null;

    if (cls != null) {

      cfg = classes.get(cls.getName());

      // BROWSE SUPER CLASSES UP TO ROOT
      while (cfg == null && cls != null) {
        cls = cls.getSuperClass();

        if (cls != null) {
          cfg = classes.get(cls.getName());

          if (cfg != null && !cfg.polymorphic) {
            // NOT POLYMORPHIC: IGNORE IT AND EXIT FROM THE LOOP
            cfg = null;
            break;
          }
        }
      }
    }

    if (cfg == null)
      // ASSIGN DEFAULT CFG (*)
      cfg = defaultConfig;

    return cfg;
  }
*/
  private String formatClassNote(final OClass cls, final String note) {
    if (note == null || note.isEmpty())
      return cls.getName();

    return (String) OVariableParser.resolveVariables(note, "${", "}", new OVariableParserListener() {
      @Override
      public Object resolve(final String iVariable) {
      	
        if (iVariable.equalsIgnoreCase("class")) {
          return cls.getName();
        }

        return null;
      }
    });
  }

  protected void logClass(final OAuditingOperation operation, final String note) {
    final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.instance().get();

    String username = null;

    final OSecurityUser user = db.getUser();
    if (user != null)
      username = user.getName();

    final ODocument doc = createLogDocument(operation, db.getName(), username, note);

    auditingQueue.offer(doc);
  }

  protected void logClass(final OAuditingOperation operation, final OClass cls) {
    if (schemaConfig != null && schemaConfig.isEnabled(operation)) {
    	logClass(operation, schemaConfig.formatMessage(operation, cls.getName()));
    }
  }

  public void onCreateClass(OClass iClass) {
    logClass(OAuditingOperation.CREATEDCLASS, iClass);
  }

  public void onDropClass(OClass iClass) {
    logClass(OAuditingOperation.DROPPEDCLASS, iClass);
  }

  public void log(final OAuditingOperation operation, final String dbName, final String username, final String message) {
    if(auditingQueue != null)
      auditingQueue.offer(createLogDocument(operation, dbName, username, message));
  }

  private ODocument createLogDocument(final OAuditingOperation operation, final String dbName, final String username, final String message) {
    ODocument doc = null;

    doc = new ODocument();
    doc.field("date", System.currentTimeMillis());
    doc.field("operation", operation.getByte());
    
    if (username != null)
      doc.field("user", username);
      
    if (message != null)
      doc.field("note", message);
      
    if (dbName != null)
      doc.field("database", dbName);

    return doc;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy