Merge changes from topic 'group-audit-log'
* changes: Add new group screen that shows the audit log of the group Add integration test for listing group audit events Allow to get audit log of a group through GroupApi Add new REST endpoint that provides the audit log of a group
This commit is contained in:
commit
07e25e2dec
@ -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
|
||||
|
@ -27,8 +27,13 @@ import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.NoHttpd;
|
||||
import com.google.gerrit.acceptance.TestAccount;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.extensions.api.groups.GroupApi;
|
||||
import com.google.gerrit.extensions.api.groups.GroupInput;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.common.GroupAuditEventInfo;
|
||||
import com.google.gerrit.extensions.common.GroupAuditEventInfo.GroupMemberAuditEventInfo;
|
||||
import com.google.gerrit.extensions.common.GroupAuditEventInfo.Type;
|
||||
import com.google.gerrit.extensions.common.GroupAuditEventInfo.UserMemberAuditEventInfo;
|
||||
import com.google.gerrit.extensions.common.GroupInfo;
|
||||
import com.google.gerrit.extensions.common.GroupOptionsInfo;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
@ -36,11 +41,13 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||
import com.google.gerrit.extensions.restapi.Url;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
import com.google.gerrit.server.group.SystemGroupBackend;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -422,6 +429,62 @@ public class GroupsIT extends AbstractDaemonTest {
|
||||
assertGroupInfo(adminGroup, Iterables.getOnlyElement(groups.values()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAuditLog() throws Exception {
|
||||
GroupApi g = gApi.groups().create(name("group"));
|
||||
List<? extends GroupAuditEventInfo> auditEvents = g.auditLog();
|
||||
assertThat(auditEvents).hasSize(1);
|
||||
assertAuditEvent(auditEvents.get(0), Type.ADD_USER, admin.id, admin.id);
|
||||
|
||||
g.addMembers(user.username);
|
||||
auditEvents = g.auditLog();
|
||||
assertThat(auditEvents).hasSize(2);
|
||||
assertAuditEvent(auditEvents.get(0), Type.ADD_USER, admin.id, user.id);
|
||||
|
||||
g.removeMembers(user.username);
|
||||
auditEvents = g.auditLog();
|
||||
assertThat(auditEvents).hasSize(3);
|
||||
assertAuditEvent(auditEvents.get(0), Type.REMOVE_USER, admin.id, user.id);
|
||||
|
||||
String otherGroup = name("otherGroup");
|
||||
gApi.groups().create(otherGroup);
|
||||
g.addGroups(otherGroup);
|
||||
auditEvents = g.auditLog();
|
||||
assertThat(auditEvents).hasSize(4);
|
||||
assertAuditEvent(auditEvents.get(0), Type.ADD_GROUP, admin.id, otherGroup);
|
||||
|
||||
g.removeGroups(otherGroup);
|
||||
auditEvents = g.auditLog();
|
||||
assertThat(auditEvents).hasSize(5);
|
||||
assertAuditEvent(auditEvents.get(0), Type.REMOVE_GROUP, admin.id, otherGroup);
|
||||
|
||||
Timestamp lastDate = null;
|
||||
for (GroupAuditEventInfo auditEvent : auditEvents) {
|
||||
if (lastDate != null) {
|
||||
assertThat(lastDate).isGreaterThan(auditEvent.date);
|
||||
}
|
||||
lastDate = auditEvent.date;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertAuditEvent(GroupAuditEventInfo info, Type expectedType,
|
||||
Account.Id expectedUser, Account.Id expectedMember) {
|
||||
assertThat(info.user._accountId).isEqualTo(expectedUser.get());
|
||||
assertThat(info.type).isEqualTo(expectedType);
|
||||
assertThat(info).isInstanceOf(UserMemberAuditEventInfo.class);
|
||||
assertThat(((UserMemberAuditEventInfo) info).member._accountId).isEqualTo(
|
||||
expectedMember.get());
|
||||
}
|
||||
|
||||
private void assertAuditEvent(GroupAuditEventInfo info, Type expectedType,
|
||||
Account.Id expectedUser, String expectedMemberGroupName) {
|
||||
assertThat(info.user._accountId).isEqualTo(expectedUser.get());
|
||||
assertThat(info.type).isEqualTo(expectedType);
|
||||
assertThat(info).isInstanceOf(GroupMemberAuditEventInfo.class);
|
||||
assertThat(((GroupMemberAuditEventInfo) info).member.name).isEqualTo(
|
||||
expectedMemberGroupName);
|
||||
}
|
||||
|
||||
private void assertMembers(String group, TestAccount... expectedMembers)
|
||||
throws Exception {
|
||||
assertMembers(
|
||||
|
@ -15,6 +15,7 @@
|
||||
package com.google.gerrit.extensions.api.groups;
|
||||
|
||||
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.common.GroupOptionsInfo;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
@ -132,4 +133,12 @@ public interface GroupApi {
|
||||
* @throws RestApiException
|
||||
*/
|
||||
void removeGroups(String... groups) throws RestApiException;
|
||||
|
||||
/**
|
||||
* Returns the audit log of the group.
|
||||
*
|
||||
* @return list of audit events of the group.
|
||||
* @throws RestApiException
|
||||
*/
|
||||
List<? extends GroupAuditEventInfo> auditLog() throws RestApiException;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -50,6 +50,7 @@ import com.google.gerrit.client.account.MyWatchedProjectsScreen;
|
||||
import com.google.gerrit.client.account.NewAgreementScreen;
|
||||
import com.google.gerrit.client.account.RegisterScreen;
|
||||
import com.google.gerrit.client.account.ValidateEmailScreen;
|
||||
import com.google.gerrit.client.admin.AccountGroupAuditLogScreen;
|
||||
import com.google.gerrit.client.admin.AccountGroupInfoScreen;
|
||||
import com.google.gerrit.client.admin.AccountGroupMembersScreen;
|
||||
import com.google.gerrit.client.admin.AccountGroupScreen;
|
||||
@ -831,6 +832,8 @@ public class Dispatcher {
|
||||
Gerrit.display(token, new AccountGroupInfoScreen(group, token));
|
||||
} else if (AccountGroupScreen.MEMBERS.equals(panel)) {
|
||||
Gerrit.display(token, new AccountGroupMembersScreen(group, token));
|
||||
} else if (AccountGroupScreen.AUDIT_LOG.equals(panel)) {
|
||||
Gerrit.display(token, new AccountGroupAuditLogScreen(group, token));
|
||||
} else {
|
||||
Gerrit.display(token, new NotFoundScreen());
|
||||
}
|
||||
|
@ -0,0 +1,153 @@
|
||||
// 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.client.admin;
|
||||
|
||||
import static com.google.gerrit.client.FormatUtil.mediumFormat;
|
||||
import static com.google.gerrit.client.FormatUtil.name;
|
||||
|
||||
import com.google.gerrit.client.Dispatcher;
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.account.AccountInfo;
|
||||
import com.google.gerrit.client.groups.GroupApi;
|
||||
import com.google.gerrit.client.groups.GroupAuditEventInfo;
|
||||
import com.google.gerrit.client.groups.GroupInfo;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.rpc.Natives;
|
||||
import com.google.gerrit.client.ui.FancyFlexTable;
|
||||
import com.google.gerrit.client.ui.Hyperlink;
|
||||
import com.google.gerrit.client.ui.SmallHeading;
|
||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
import com.google.gwt.user.client.ui.Anchor;
|
||||
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AccountGroupAuditLogScreen extends AccountGroupScreen {
|
||||
private AuditEventTable auditEventTable;
|
||||
|
||||
public AccountGroupAuditLogScreen(GroupInfo toShow, String token) {
|
||||
super(toShow, token);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitUI() {
|
||||
super.onInitUI();
|
||||
add(new SmallHeading(Util.C.headingAuditLog()));
|
||||
auditEventTable = new AuditEventTable();
|
||||
add(auditEventTable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void display(GroupInfo group, boolean canModify) {
|
||||
GroupApi.getAuditLog(group.getGroupUUID(),
|
||||
new GerritCallback<JsArray<GroupAuditEventInfo>>() {
|
||||
@Override
|
||||
public void onSuccess(JsArray<GroupAuditEventInfo> result) {
|
||||
auditEventTable.display(Natives.asList(result));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class AuditEventTable extends FancyFlexTable<GroupAuditEventInfo> {
|
||||
AuditEventTable() {
|
||||
table.setText(0, 1, Util.C.columnDate());
|
||||
table.setText(0, 2, Util.C.columnType());
|
||||
table.setText(0, 3, Util.C.columnMember());
|
||||
table.setText(0, 4, Util.C.columnByUser());
|
||||
|
||||
FlexCellFormatter fmt = table.getFlexCellFormatter();
|
||||
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().dataHeader());
|
||||
fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
|
||||
fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
|
||||
fmt.addStyleName(0, 4, Gerrit.RESOURCES.css().dataHeader());
|
||||
}
|
||||
|
||||
void display(List<GroupAuditEventInfo> auditEvents) {
|
||||
while (1 < table.getRowCount()) {
|
||||
table.removeRow(table.getRowCount() - 1);
|
||||
}
|
||||
|
||||
for (GroupAuditEventInfo auditEvent : auditEvents) {
|
||||
int row = table.getRowCount();
|
||||
table.insertRow(row);
|
||||
applyDataRowStyle(row);
|
||||
populate(row, auditEvent);
|
||||
}
|
||||
}
|
||||
|
||||
void populate(int row, GroupAuditEventInfo auditEvent) {
|
||||
FlexCellFormatter fmt = table.getFlexCellFormatter();
|
||||
table.setText(row, 1, mediumFormat(auditEvent.date()));
|
||||
|
||||
switch (auditEvent.type()) {
|
||||
case ADD_USER:
|
||||
case ADD_GROUP:
|
||||
table.setText(row, 2, Util.C.typeAdded());
|
||||
break;
|
||||
case REMOVE_USER:
|
||||
case REMOVE_GROUP:
|
||||
table.setText(row, 2, Util.C.typeRemoved());
|
||||
break;
|
||||
}
|
||||
|
||||
switch (auditEvent.type()) {
|
||||
case ADD_USER:
|
||||
case REMOVE_USER:
|
||||
table.setText(row, 3, formatAccount(auditEvent.memberAsUser()));
|
||||
break;
|
||||
case ADD_GROUP:
|
||||
case REMOVE_GROUP:
|
||||
GroupInfo member = auditEvent.memberAsGroup();
|
||||
if (AccountGroup.isInternalGroup(member.getGroupUUID())) {
|
||||
table.setWidget(row, 3,
|
||||
new Hyperlink(member.name(),
|
||||
Dispatcher.toGroup(member.getGroupUUID())));
|
||||
fmt.getElement(row, 3).setTitle(null);
|
||||
} else if (member.url() != null) {
|
||||
Anchor a = new Anchor();
|
||||
a.setText(member.name());
|
||||
a.setHref(member.url());
|
||||
a.setTitle("UUID " + member.getGroupUUID().get());
|
||||
table.setWidget(row, 3, a);
|
||||
fmt.getElement(row, 3).setTitle(null);
|
||||
} else {
|
||||
table.setText(row, 3, member.name());
|
||||
fmt.getElement(row, 3).setTitle(
|
||||
"UUID " + member.getGroupUUID().get());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
table.setText(row, 4, formatAccount(auditEvent.user()));
|
||||
|
||||
fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().dataCell());
|
||||
fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
|
||||
fmt.addStyleName(row, 3, Gerrit.RESOURCES.css().dataCell());
|
||||
fmt.addStyleName(row, 4, Gerrit.RESOURCES.css().dataCell());
|
||||
|
||||
setRowItem(row, auditEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatAccount(AccountInfo account) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(name(account));
|
||||
b.append(" (");
|
||||
b.append(account._accountId());
|
||||
b.append(")");
|
||||
return b.toString();
|
||||
}
|
||||
}
|
@ -25,15 +25,20 @@ import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
public abstract class AccountGroupScreen extends MenuScreen {
|
||||
public static final String INFO = "info";
|
||||
public static final String MEMBERS = "members";
|
||||
public static final String AUDIT_LOG = "audit-log";
|
||||
|
||||
private final GroupInfo group;
|
||||
private final String token;
|
||||
private final String membersTabToken;
|
||||
private final String auditLogTabToken;
|
||||
|
||||
public AccountGroupScreen(final GroupInfo toShow, final String token) {
|
||||
setRequiresSignIn(true);
|
||||
|
||||
this.group = toShow;
|
||||
this.token = token;
|
||||
this.membersTabToken = getTabToken(token, MEMBERS);
|
||||
this.auditLogTabToken = getTabToken(token, AUDIT_LOG);
|
||||
|
||||
link(Util.C.groupTabGeneral(), getTabToken(token, INFO));
|
||||
link(Util.C.groupTabMembers(), membersTabToken,
|
||||
@ -56,6 +61,11 @@ public abstract class AccountGroupScreen extends MenuScreen {
|
||||
GroupApi.isGroupOwner(group.name(), new GerritCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
if (result) {
|
||||
link(Util.C.groupTabAuditLog(), auditLogTabToken,
|
||||
AccountGroup.isInternalGroup(group.getGroupUUID()));
|
||||
setToken(token);
|
||||
}
|
||||
display(group, result);
|
||||
}
|
||||
});
|
||||
|
@ -68,6 +68,7 @@ public interface AdminConstants extends Constants {
|
||||
String headingParentProjectName();
|
||||
String columnProjectName();
|
||||
String headingAgreements();
|
||||
String headingAuditLog();
|
||||
|
||||
String headingProjectSubmitType();
|
||||
String projectSubmitType_FAST_FORWARD_ONLY();
|
||||
@ -89,6 +90,13 @@ public interface AdminConstants extends Constants {
|
||||
String columnGroupNotifications();
|
||||
String columnGroupVisibleToAll();
|
||||
|
||||
String columnDate();
|
||||
String columnType();
|
||||
String columnByUser();
|
||||
|
||||
String typeAdded();
|
||||
String typeRemoved();
|
||||
|
||||
String columnBranchName();
|
||||
String columnBranchRevision();
|
||||
String initialRevision();
|
||||
@ -104,6 +112,7 @@ public interface AdminConstants extends Constants {
|
||||
String createGroupTitle();
|
||||
String groupTabGeneral();
|
||||
String groupTabMembers();
|
||||
String groupTabAuditLog();
|
||||
String projectListTitle();
|
||||
String projectFilter();
|
||||
String createProjectTitle();
|
||||
|
@ -47,6 +47,7 @@ noMembersInfo = Group Members can only be viewed for Gerrit internal groups. For
|
||||
headingExternalGroup = Selected External Group
|
||||
headingCreateGroup = Create New Group
|
||||
headingAgreements = Contributor Agreements
|
||||
headingAuditLog = Audit Log
|
||||
|
||||
headingProjectSubmitType = Submit Type
|
||||
projectSubmitType_FAST_FORWARD_ONLY = Fast Forward Only
|
||||
@ -68,6 +69,13 @@ columnGroupType = Group Type
|
||||
columnGroupNotifications = Email Only Authors
|
||||
columnGroupVisibleToAll = Visible To All
|
||||
|
||||
columnDate = Date
|
||||
columnType = Type
|
||||
columnByUser = By User
|
||||
|
||||
typeAdded = Added
|
||||
typeRemoved = Removed
|
||||
|
||||
columnBranchName = Branch Name
|
||||
columnBranchRevision = Revision
|
||||
initialRevision = Initial Revision
|
||||
@ -83,6 +91,7 @@ groupFilter = Filter
|
||||
createGroupTitle = Create Group
|
||||
groupTabGeneral = General
|
||||
groupTabMembers = Members
|
||||
groupTabAuditLog = Audit Log
|
||||
projectListTitle = Projects
|
||||
projectFilter = Filter
|
||||
createProjectTitle = Create Project
|
||||
|
@ -193,6 +193,12 @@ public class GroupApi {
|
||||
}
|
||||
}
|
||||
|
||||
/** Get audit log of a group. */
|
||||
public static void getAuditLog(AccountGroup.UUID group,
|
||||
AsyncCallback<JsArray<GroupAuditEventInfo>> cb) {
|
||||
group(group).view("log.audit").get(cb);
|
||||
}
|
||||
|
||||
private static RestApi members(AccountGroup.UUID group) {
|
||||
return group(group).view("members");
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
// 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.client.groups;
|
||||
|
||||
import com.google.gerrit.client.account.AccountInfo;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
public class GroupAuditEventInfo extends JavaScriptObject {
|
||||
public enum Type {
|
||||
ADD_USER, REMOVE_USER, ADD_GROUP, REMOVE_GROUP
|
||||
}
|
||||
|
||||
public final Timestamp date() {
|
||||
return JavaSqlTimestamp_JsonSerializer.parseTimestamp(dateRaw());
|
||||
}
|
||||
|
||||
public final Type type() {
|
||||
return Type.valueOf(typeRaw());
|
||||
}
|
||||
|
||||
public final native AccountInfo user() /*-{ return this.user; }-*/;
|
||||
public final native AccountInfo memberAsUser() /*-{ return this.member; }-*/;
|
||||
public final native GroupInfo memberAsGroup() /*-{ return this.member; }-*/;
|
||||
|
||||
private final native String dateRaw() /*-{ return this.date; }-*/;
|
||||
private final native String typeRaw() /*-{ return this.type; }-*/;
|
||||
|
||||
protected GroupAuditEventInfo() {
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package com.google.gerrit.server.api.groups;
|
||||
import com.google.gerrit.common.errors.NoSuchGroupException;
|
||||
import com.google.gerrit.extensions.api.groups.GroupApi;
|
||||
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.common.GroupOptionsInfo;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
@ -25,6 +26,7 @@ import com.google.gerrit.server.group.AddIncludedGroups;
|
||||
import com.google.gerrit.server.group.AddMembers;
|
||||
import com.google.gerrit.server.group.DeleteIncludedGroups;
|
||||
import com.google.gerrit.server.group.DeleteMembers;
|
||||
import com.google.gerrit.server.group.GetAuditLog;
|
||||
import com.google.gerrit.server.group.GetDescription;
|
||||
import com.google.gerrit.server.group.GetDetail;
|
||||
import com.google.gerrit.server.group.GetGroup;
|
||||
@ -67,6 +69,7 @@ class GroupApiImpl implements GroupApi {
|
||||
private final ListIncludedGroups listGroups;
|
||||
private final AddIncludedGroups addGroups;
|
||||
private final DeleteIncludedGroups deleteGroups;
|
||||
private final GetAuditLog getAuditLog;
|
||||
private final GroupResource rsrc;
|
||||
|
||||
@AssistedInject
|
||||
@ -87,6 +90,7 @@ class GroupApiImpl implements GroupApi {
|
||||
ListIncludedGroups listGroups,
|
||||
AddIncludedGroups addGroups,
|
||||
DeleteIncludedGroups deleteGroups,
|
||||
GetAuditLog getAuditLog,
|
||||
@Assisted GroupResource rsrc) {
|
||||
this.getGroup = getGroup;
|
||||
this.getDetail = getDetail;
|
||||
@ -104,6 +108,7 @@ class GroupApiImpl implements GroupApi {
|
||||
this.listGroups = listGroups;
|
||||
this.addGroups = addGroups;
|
||||
this.deleteGroups = deleteGroups;
|
||||
this.getAuditLog = getAuditLog;
|
||||
this.rsrc = rsrc;
|
||||
}
|
||||
|
||||
@ -258,4 +263,12 @@ class GroupApiImpl implements GroupApi {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GroupAuditEventInfo> auditLog() throws RestApiException {
|
||||
try {
|
||||
return getAuditLog.apply(rsrc);
|
||||
} catch (OrmException e) {
|
||||
throw new RestApiException("Cannot get audit log", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user