Add new REST endpoint that provides the audit log of a group
Bug: Issue 1479 Change-Id: I59c51964cab1f1ab32f50db8645a09d96d7a0196 Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
		@@ -592,6 +592,89 @@ describes the new owner group.
 | 
			
		||||
  }
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
[[get-audit-log]]
 | 
			
		||||
=== Get Audit Log
 | 
			
		||||
--
 | 
			
		||||
'GET /groups/link:#group-id[\{group-id\}]/log.audit'
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
Gets the audit log of a Gerrit internal group.
 | 
			
		||||
 | 
			
		||||
.Request
 | 
			
		||||
----
 | 
			
		||||
  GET /groups/9999c971bb4ab872aab759d8c49833ee6b9ff320/log.audit HTTP/1.0
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
As response a list of link:#group-audit-event-info[GroupAuditEventInfo]
 | 
			
		||||
entities is returned that describes the audit events of the group. The
 | 
			
		||||
returned audit events are sorted by date in reverse order so that the
 | 
			
		||||
newest audit event comes first.
 | 
			
		||||
 | 
			
		||||
.Response
 | 
			
		||||
----
 | 
			
		||||
  HTTP/1.1 200 OK
 | 
			
		||||
  Content-Disposition: attachment
 | 
			
		||||
  Content-Type: application/json; charset=UTF-8
 | 
			
		||||
 | 
			
		||||
  )]}'
 | 
			
		||||
  [
 | 
			
		||||
    {
 | 
			
		||||
      "member": {
 | 
			
		||||
        "url": "#/admin/groups/uuid-fdda826a0815859ab48d22a05a43472f0f55f89a",
 | 
			
		||||
        "options": {},
 | 
			
		||||
        "group_id": 3,
 | 
			
		||||
        "owner": "Administrators",
 | 
			
		||||
        "owner_id": "e56678641565e7f59dd5c6878f5bcbc842bf150a",
 | 
			
		||||
        "id": "fdda826a0815859ab48d22a05a43472f0f55f89a",
 | 
			
		||||
        "name": "MyGroup"
 | 
			
		||||
      },
 | 
			
		||||
      "type": "REMOVE_GROUP",
 | 
			
		||||
      "user": {
 | 
			
		||||
        "_account_id": 1000000,
 | 
			
		||||
        "name": "Administrator",
 | 
			
		||||
        "email": "admin@example.com",
 | 
			
		||||
        "username": "admin"
 | 
			
		||||
      },
 | 
			
		||||
      "date": "2015-07-03 09:22:26.348000000"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "member": {
 | 
			
		||||
        "url": "#/admin/groups/uuid-fdda826a0815859ab48d22a05a43472f0f55f89a",
 | 
			
		||||
        "options": {},
 | 
			
		||||
        "group_id": 3,
 | 
			
		||||
        "owner": "Administrators",
 | 
			
		||||
        "owner_id": "e56678641565e7f59dd5c6878f5bcbc842bf150a",
 | 
			
		||||
        "id": "fdda826a0815859ab48d22a05a43472f0f55f89a",
 | 
			
		||||
        "name": "MyGroup"
 | 
			
		||||
      },
 | 
			
		||||
      "type": "ADD_GROUP",
 | 
			
		||||
      "user": {
 | 
			
		||||
        "_account_id": 1000000,
 | 
			
		||||
        "name": "Administrator",
 | 
			
		||||
        "email": "admin@example.com",
 | 
			
		||||
        "username": "admin"
 | 
			
		||||
      },
 | 
			
		||||
      "date": "2015-07-03 08:43:36.592000000"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "member": {
 | 
			
		||||
        "_account_id": 1000000,
 | 
			
		||||
        "name": "Administrator",
 | 
			
		||||
        "email": "admin@example.com",
 | 
			
		||||
        "username": "admin"
 | 
			
		||||
      },
 | 
			
		||||
      "type": "ADD_USER",
 | 
			
		||||
      "user": {
 | 
			
		||||
        "_account_id": 1000001,
 | 
			
		||||
        "name": "John Doe",
 | 
			
		||||
        "email": "john.doe@example.com",
 | 
			
		||||
        "username": "jdoe"
 | 
			
		||||
      },
 | 
			
		||||
      "date": "2015-07-01 13:36:36.602000000"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
[[group-member-endpoints]]
 | 
			
		||||
== Group Member Endpoints
 | 
			
		||||
 | 
			
		||||
@@ -1108,6 +1191,38 @@ Group name that uniquely identifies one group.
 | 
			
		||||
[[json-entities]]
 | 
			
		||||
== JSON Entities
 | 
			
		||||
 | 
			
		||||
[[group-audit-event-info]]
 | 
			
		||||
=== GroupAuditEventInfo
 | 
			
		||||
The `GroupAuditEventInfo` entity contains information about an audit
 | 
			
		||||
event of a group.
 | 
			
		||||
 | 
			
		||||
[options="header",cols="1,6"]
 | 
			
		||||
|======================
 | 
			
		||||
|Field Name|Description
 | 
			
		||||
|`member`  |
 | 
			
		||||
The group member that is added/removed. If `type` is `ADD_USER` or
 | 
			
		||||
`REMOVE_USER` the member is returned as detailed
 | 
			
		||||
link:rest-api-accounts.html#account-info[AccountInfo] entity, if `type`
 | 
			
		||||
is `ADD_GROUP` or `REMOVE_GROUP` the member is returned as
 | 
			
		||||
link:#group-info[GroupInfo] entity.
 | 
			
		||||
|`type`    |
 | 
			
		||||
The event type, can be: `ADD_USER`, `REMOVE_USER`, `ADD_GROUP` or
 | 
			
		||||
`REMOVE_GROUP`.
 | 
			
		||||
 | 
			
		||||
`ADD_USER`: A user was added as member to the group.
 | 
			
		||||
 | 
			
		||||
`REMOVE_USER`: A user member was removed from the group.
 | 
			
		||||
 | 
			
		||||
`ADD_GROUP`: A group was included as member in the group.
 | 
			
		||||
 | 
			
		||||
`REMOVE_GROUP`: An included group was removed from the group.
 | 
			
		||||
|`user`    |
 | 
			
		||||
The user that did the add/remove as detailed
 | 
			
		||||
link:rest-api-accounts.html#account-info[AccountInfo] entity.
 | 
			
		||||
|`date`    |
 | 
			
		||||
The timestamp of the event.
 | 
			
		||||
|======================
 | 
			
		||||
 | 
			
		||||
[[group-info]]
 | 
			
		||||
=== GroupInfo
 | 
			
		||||
The `GroupInfo` entity contains information about a group. This can be
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,74 @@
 | 
			
		||||
// Copyright (C) 2015 The Android Open Source Project
 | 
			
		||||
//
 | 
			
		||||
// 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.gerrit.extensions.common;
 | 
			
		||||
 | 
			
		||||
import java.sql.Timestamp;
 | 
			
		||||
 | 
			
		||||
public abstract class GroupAuditEventInfo {
 | 
			
		||||
  public enum Type {
 | 
			
		||||
    ADD_USER, REMOVE_USER, ADD_GROUP, REMOVE_GROUP
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Type type;
 | 
			
		||||
  public AccountInfo user;
 | 
			
		||||
  public Timestamp date;
 | 
			
		||||
 | 
			
		||||
  public static UserMemberAuditEventInfo createAddUserEvent(AccountInfo user,
 | 
			
		||||
      Timestamp date, AccountInfo member) {
 | 
			
		||||
    return new UserMemberAuditEventInfo(Type.ADD_USER, user, date, member);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static UserMemberAuditEventInfo createRemoveUserEvent(
 | 
			
		||||
      AccountInfo user, Timestamp date, AccountInfo member) {
 | 
			
		||||
    return new UserMemberAuditEventInfo(Type.REMOVE_USER, user, date, member);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static GroupMemberAuditEventInfo createAddGroupEvent(AccountInfo user,
 | 
			
		||||
      Timestamp date, GroupInfo member) {
 | 
			
		||||
    return new GroupMemberAuditEventInfo(Type.ADD_GROUP, user, date, member);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static GroupMemberAuditEventInfo createRemoveGroupEvent(
 | 
			
		||||
      AccountInfo user, Timestamp date, GroupInfo member) {
 | 
			
		||||
    return new GroupMemberAuditEventInfo(Type.REMOVE_GROUP, user, date, member);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected GroupAuditEventInfo(Type type, AccountInfo user,
 | 
			
		||||
      Timestamp date) {
 | 
			
		||||
    this.type = type;
 | 
			
		||||
    this.user = user;
 | 
			
		||||
    this.date = date;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static class UserMemberAuditEventInfo extends GroupAuditEventInfo {
 | 
			
		||||
    public AccountInfo member;
 | 
			
		||||
 | 
			
		||||
    public UserMemberAuditEventInfo(Type type, AccountInfo user,
 | 
			
		||||
        Timestamp date, AccountInfo member) {
 | 
			
		||||
      super(type, user, date);
 | 
			
		||||
      this.member = member;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static class GroupMemberAuditEventInfo extends GroupAuditEventInfo {
 | 
			
		||||
    public GroupInfo member;
 | 
			
		||||
 | 
			
		||||
    public GroupMemberAuditEventInfo(Type type, AccountInfo user,
 | 
			
		||||
        Timestamp date, GroupInfo member) {
 | 
			
		||||
      super(type, user, date);
 | 
			
		||||
      this.member = member;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -98,4 +98,16 @@ public final class AccountGroupByIdAud {
 | 
			
		||||
    removedBy = deleter;
 | 
			
		||||
    removedOn = when;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Account.Id getAddedBy() {
 | 
			
		||||
    return addedBy;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Account.Id getRemovedBy() {
 | 
			
		||||
    return removedBy;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Timestamp getRemovedOn() {
 | 
			
		||||
    return removedOn;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -103,4 +103,16 @@ public final class AccountGroupMemberAudit {
 | 
			
		||||
    removedBy = addedBy;
 | 
			
		||||
    removedOn = key.addedOn;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Account.Id getAddedBy() {
 | 
			
		||||
    return addedBy;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Account.Id getRemovedBy() {
 | 
			
		||||
    return removedBy;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Timestamp getRemovedOn() {
 | 
			
		||||
    return removedOn;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,4 +32,8 @@ public interface AccountGroupByIdAudAccess extends
 | 
			
		||||
  @Query("WHERE key.groupId = ? AND key.includeUUID = ?")
 | 
			
		||||
  ResultSet<AccountGroupByIdAud> byGroupInclude(AccountGroup.Id groupId,
 | 
			
		||||
      AccountGroup.UUID incGroupUUID) throws OrmException;
 | 
			
		||||
 | 
			
		||||
  @Query("WHERE key.groupId = ?")
 | 
			
		||||
  ResultSet<AccountGroupByIdAud> byGroup(AccountGroup.Id groupId)
 | 
			
		||||
      throws OrmException;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,4 +33,8 @@ public interface AccountGroupMemberAuditAccess extends
 | 
			
		||||
  @Query("WHERE key.groupId = ? AND key.accountId = ?")
 | 
			
		||||
  ResultSet<AccountGroupMemberAudit> byGroupAccount(AccountGroup.Id groupId,
 | 
			
		||||
      Account.Id accountId) throws OrmException;
 | 
			
		||||
 | 
			
		||||
  @Query("WHERE key.groupId = ?")
 | 
			
		||||
  ResultSet<AccountGroupMemberAudit> byGroup(AccountGroup.Id groupId)
 | 
			
		||||
      throws OrmException;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,130 @@
 | 
			
		||||
// Copyright (C) 2015 The Android Open Source Project
 | 
			
		||||
//
 | 
			
		||||
// 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.gerrit.server.group;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.common.data.GroupDescriptions;
 | 
			
		||||
import com.google.gerrit.extensions.common.AccountInfo;
 | 
			
		||||
import com.google.gerrit.extensions.common.GroupAuditEventInfo;
 | 
			
		||||
import com.google.gerrit.extensions.common.GroupInfo;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.AuthException;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.RestReadView;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.Url;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountGroup;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountGroupByIdAud;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.account.AccountLoader;
 | 
			
		||||
import com.google.gerrit.server.account.GroupCache;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
import com.google.inject.Singleton;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Comparator;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
public class GetAuditLog implements RestReadView<GroupResource> {
 | 
			
		||||
  private final Provider<ReviewDb> db;
 | 
			
		||||
  private final AccountLoader.Factory accountLoaderFactory;
 | 
			
		||||
  private final GroupCache groupCache;
 | 
			
		||||
  private final GroupJson groupJson;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  public GetAuditLog(Provider<ReviewDb> db,
 | 
			
		||||
      AccountLoader.Factory accountLoaderFactory,
 | 
			
		||||
      GroupCache groupCache,
 | 
			
		||||
      GroupJson groupJson) {
 | 
			
		||||
    this.db = db;
 | 
			
		||||
    this.accountLoaderFactory = accountLoaderFactory;
 | 
			
		||||
    this.groupCache = groupCache;
 | 
			
		||||
    this.groupJson = groupJson;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public List<? extends GroupAuditEventInfo> apply(GroupResource rsrc)
 | 
			
		||||
      throws AuthException, ResourceNotFoundException,
 | 
			
		||||
      MethodNotAllowedException, OrmException {
 | 
			
		||||
    if (rsrc.toAccountGroup() == null) {
 | 
			
		||||
      throw new MethodNotAllowedException();
 | 
			
		||||
    } else if (!rsrc.getControl().isOwner()) {
 | 
			
		||||
      throw new AuthException("Not group owner");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    AccountGroup group = db.get().accountGroups().get(
 | 
			
		||||
        rsrc.toAccountGroup().getId());
 | 
			
		||||
    if (group == null) {
 | 
			
		||||
      throw new ResourceNotFoundException();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    AccountLoader accountLoader = accountLoaderFactory.create(true);
 | 
			
		||||
 | 
			
		||||
    List<GroupAuditEventInfo> auditEvents = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    for (AccountGroupMemberAudit auditEvent :
 | 
			
		||||
      db.get().accountGroupMembersAudit().byGroup(group.getId()).toList()) {
 | 
			
		||||
      AccountInfo member = accountLoader.get(auditEvent.getKey().getParentKey());
 | 
			
		||||
 | 
			
		||||
      auditEvents.add(GroupAuditEventInfo.createAddUserEvent(
 | 
			
		||||
          accountLoader.get(auditEvent.getAddedBy()),
 | 
			
		||||
          auditEvent.getKey().getAddedOn(), member));
 | 
			
		||||
 | 
			
		||||
      if (!auditEvent.isActive()) {
 | 
			
		||||
        auditEvents.add(GroupAuditEventInfo.createRemoveUserEvent(
 | 
			
		||||
            accountLoader.get(auditEvent.getRemovedBy()),
 | 
			
		||||
            auditEvent.getRemovedOn(), member));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (AccountGroupByIdAud auditEvent :
 | 
			
		||||
        db.get().accountGroupByIdAud().byGroup(group.getId()).toList()) {
 | 
			
		||||
      AccountGroup.UUID includedGroupUUID = auditEvent.getKey().getIncludeUUID();
 | 
			
		||||
      AccountGroup includedGroup = groupCache.get(includedGroupUUID);
 | 
			
		||||
      GroupInfo member;
 | 
			
		||||
      if (includedGroup != null) {
 | 
			
		||||
        member = groupJson.format(GroupDescriptions.forAccountGroup(includedGroup));
 | 
			
		||||
      } else {
 | 
			
		||||
        member = new GroupInfo();
 | 
			
		||||
        member.id = Url.encode(includedGroupUUID.get());
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      auditEvents.add(GroupAuditEventInfo.createAddGroupEvent(
 | 
			
		||||
          accountLoader.get(auditEvent.getAddedBy()),
 | 
			
		||||
          auditEvent.getKey().getAddedOn(), member));
 | 
			
		||||
 | 
			
		||||
      if (!auditEvent.isActive()) {
 | 
			
		||||
        auditEvents.add(GroupAuditEventInfo.createRemoveGroupEvent(
 | 
			
		||||
            accountLoader.get(auditEvent.getRemovedBy()),
 | 
			
		||||
            auditEvent.getRemovedOn(), member));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    accountLoader.fill();
 | 
			
		||||
 | 
			
		||||
    // sort by date in reverse order so that the newest audit event comes first
 | 
			
		||||
    Collections.sort(auditEvents, new Comparator<GroupAuditEventInfo>() {
 | 
			
		||||
      @Override
 | 
			
		||||
      public int compare(GroupAuditEventInfo e1, GroupAuditEventInfo e2) {
 | 
			
		||||
        return e2.date.compareTo(e1.date);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return auditEvents;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -55,6 +55,7 @@ public class Module extends RestApiModule {
 | 
			
		||||
    put(GROUP_KIND, "owner").to(PutOwner.class);
 | 
			
		||||
    get(GROUP_KIND, "options").to(GetOptions.class);
 | 
			
		||||
    put(GROUP_KIND, "options").to(PutOptions.class);
 | 
			
		||||
    get(GROUP_KIND, "log.audit").to(GetAuditLog.class);
 | 
			
		||||
 | 
			
		||||
    child(GROUP_KIND, "members").to(MembersCollection.class);
 | 
			
		||||
    get(MEMBER_KIND).to(GetMember.class);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user