Audit hooks on JSON/RPC and SSH commands using Gerrit plugins.
New @Audit annotation to enable invocation of AuditService injected on GerritGlobalModule. Annotation applies to JSON/RPC interfaces whilst on the SSH side audit is "hooked" directly into SshLog class. Enables the integration of Audit plugins through the implementation of audit-listeners. Dynamic loading and unloading of audit plugins is supported through the @Extension/@Listener association and automatically loaded and unloaded using Plugin module self-registration. In order to implement a new AuditListener implementation you only to: 1) Define an implementation of AuditListener and annotate as: @Listener public class MyAuditTrail extends AuditListener 2) Define a Plugin Module to bind your Audit implementation in the configure() method as: DynamicSet.bind(binder(), AuditListener.class) .to(MyAuditTrail.class); Change-Id: Iaa26c4687a4ef4cbe27fe8396a5e0b8f6627536f Signed-off-by: Luca Milanesio <luca.milanesio@gmail.com>
This commit is contained in:

committed by
Edwin Kempin

parent
d5e87c3aad
commit
27ba2ac5e6
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (C) 2012 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.common.audit;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audit annotation for JSON/RPC interfaces.
|
||||||
|
*
|
||||||
|
* Flag with @Audit all the JSON/RPC methods to
|
||||||
|
* be traced in audit-trail and submitted to the
|
||||||
|
* AuditService.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.METHOD})
|
||||||
|
public @interface Audit {
|
||||||
|
String action() default "";
|
||||||
|
|
||||||
|
/** List of positions of parameters to be obfuscated in audit-trail (i.e. passwords) */
|
||||||
|
int[] obfuscate() default {};
|
||||||
|
}
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.common.auth.userpass;
|
package com.google.gerrit.common.auth.userpass;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.audit.Audit;
|
||||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||||
import com.google.gwtjsonrpc.common.AllowCrossSiteRequest;
|
import com.google.gwtjsonrpc.common.AllowCrossSiteRequest;
|
||||||
import com.google.gwtjsonrpc.common.RemoteJsonService;
|
import com.google.gwtjsonrpc.common.RemoteJsonService;
|
||||||
@@ -22,6 +23,7 @@ import com.google.gwtjsonrpc.common.RpcImpl.Version;
|
|||||||
|
|
||||||
@RpcImpl(version = Version.V2_0)
|
@RpcImpl(version = Version.V2_0)
|
||||||
public interface UserPassAuthService extends RemoteJsonService {
|
public interface UserPassAuthService extends RemoteJsonService {
|
||||||
|
@Audit(action = "sign in", obfuscate={1})
|
||||||
@AllowCrossSiteRequest
|
@AllowCrossSiteRequest
|
||||||
void authenticate(String username, String password,
|
void authenticate(String username, String password,
|
||||||
AsyncCallback<LoginResult> callback);
|
AsyncCallback<LoginResult> callback);
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.common.data;
|
package com.google.gerrit.common.data;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.audit.Audit;
|
||||||
import com.google.gerrit.common.auth.SignInRequired;
|
import com.google.gerrit.common.auth.SignInRequired;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
|
import com.google.gerrit.reviewdb.client.AccountExternalId;
|
||||||
@@ -33,20 +34,25 @@ public interface AccountSecurity extends RemoteJsonService {
|
|||||||
@SignInRequired
|
@SignInRequired
|
||||||
void mySshKeys(AsyncCallback<List<AccountSshKey>> callback);
|
void mySshKeys(AsyncCallback<List<AccountSshKey>> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void addSshKey(String keyText, AsyncCallback<AccountSshKey> callback);
|
void addSshKey(String keyText, AsyncCallback<AccountSshKey> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void deleteSshKeys(Set<AccountSshKey.Id> ids,
|
void deleteSshKeys(Set<AccountSshKey.Id> ids,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void changeUserName(String newName, AsyncCallback<VoidResult> callback);
|
void changeUserName(String newName, AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void generatePassword(AccountExternalId.Key key,
|
void generatePassword(AccountExternalId.Key key,
|
||||||
AsyncCallback<AccountExternalId> callback);
|
AsyncCallback<AccountExternalId> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void clearPassword(AccountExternalId.Key key,
|
void clearPassword(AccountExternalId.Key key,
|
||||||
AsyncCallback<AccountExternalId> gerritCallback);
|
AsyncCallback<AccountExternalId> gerritCallback);
|
||||||
@@ -57,21 +63,26 @@ public interface AccountSecurity extends RemoteJsonService {
|
|||||||
@SignInRequired
|
@SignInRequired
|
||||||
void myGroups(AsyncCallback<List<GroupDetail>> callback);
|
void myGroups(AsyncCallback<List<GroupDetail>> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void deleteExternalIds(Set<AccountExternalId.Key> keys,
|
void deleteExternalIds(Set<AccountExternalId.Key> keys,
|
||||||
AsyncCallback<Set<AccountExternalId.Key>> callback);
|
AsyncCallback<Set<AccountExternalId.Key>> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void updateContact(String fullName, String emailAddr,
|
void updateContact(String fullName, String emailAddr,
|
||||||
ContactInformation info, AsyncCallback<Account> callback);
|
ContactInformation info, AsyncCallback<Account> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void enterAgreement(String agreementName,
|
void enterAgreement(String agreementName,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void registerEmail(String address, AsyncCallback<Account> callback);
|
void registerEmail(String address, AsyncCallback<Account> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void validateEmail(String token, AsyncCallback<VoidResult> callback);
|
void validateEmail(String token, AsyncCallback<VoidResult> callback);
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.common.data;
|
package com.google.gerrit.common.data;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.audit.Audit;
|
||||||
import com.google.gerrit.common.auth.SignInRequired;
|
import com.google.gerrit.common.auth.SignInRequired;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
||||||
@@ -36,10 +37,12 @@ public interface AccountService extends RemoteJsonService {
|
|||||||
@SignInRequired
|
@SignInRequired
|
||||||
void myDiffPreferences(AsyncCallback<AccountDiffPreference> callback);
|
void myDiffPreferences(AsyncCallback<AccountDiffPreference> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void changePreferences(AccountGeneralPreferences pref,
|
void changePreferences(AccountGeneralPreferences pref,
|
||||||
AsyncCallback<VoidResult> gerritCallback);
|
AsyncCallback<VoidResult> gerritCallback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void changeDiffPreferences(AccountDiffPreference diffPref,
|
void changeDiffPreferences(AccountDiffPreference diffPref,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
@@ -47,14 +50,17 @@ public interface AccountService extends RemoteJsonService {
|
|||||||
@SignInRequired
|
@SignInRequired
|
||||||
void myProjectWatch(AsyncCallback<List<AccountProjectWatchInfo>> callback);
|
void myProjectWatch(AsyncCallback<List<AccountProjectWatchInfo>> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void addProjectWatch(String projectName, String filter,
|
void addProjectWatch(String projectName, String filter,
|
||||||
AsyncCallback<AccountProjectWatchInfo> callback);
|
AsyncCallback<AccountProjectWatchInfo> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void updateProjectWatch(AccountProjectWatch watch,
|
void updateProjectWatch(AccountProjectWatch watch,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void deleteProjectWatches(Set<AccountProjectWatch.Key> keys,
|
void deleteProjectWatches(Set<AccountProjectWatch.Key> keys,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.common.data;
|
package com.google.gerrit.common.data;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.audit.Audit;
|
||||||
import com.google.gerrit.common.auth.SignInRequired;
|
import com.google.gerrit.common.auth.SignInRequired;
|
||||||
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
@@ -25,12 +26,16 @@ import com.google.gwtjsonrpc.common.RpcImpl.Version;
|
|||||||
|
|
||||||
@RpcImpl(version = Version.V2_0)
|
@RpcImpl(version = Version.V2_0)
|
||||||
public interface ChangeDetailService extends RemoteJsonService {
|
public interface ChangeDetailService extends RemoteJsonService {
|
||||||
|
@Audit
|
||||||
void changeDetail(Change.Id id, AsyncCallback<ChangeDetail> callback);
|
void changeDetail(Change.Id id, AsyncCallback<ChangeDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
void includedInDetail(Change.Id id, AsyncCallback<IncludedInDetail> callback);
|
void includedInDetail(Change.Id id, AsyncCallback<IncludedInDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
void patchSetDetail(PatchSet.Id key, AsyncCallback<PatchSetDetail> callback);
|
void patchSetDetail(PatchSet.Id key, AsyncCallback<PatchSetDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
void patchSetDetail2(PatchSet.Id baseId, PatchSet.Id key,
|
void patchSetDetail2(PatchSet.Id baseId, PatchSet.Id key,
|
||||||
AccountDiffPreference diffPrefs, AsyncCallback<PatchSetDetail> callback);
|
AccountDiffPreference diffPrefs, AsyncCallback<PatchSetDetail> callback);
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.common.data;
|
package com.google.gerrit.common.data;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.audit.Audit;
|
||||||
import com.google.gerrit.common.auth.SignInRequired;
|
import com.google.gerrit.common.auth.SignInRequired;
|
||||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||||
import com.google.gwtjsonrpc.common.RemoteJsonService;
|
import com.google.gwtjsonrpc.common.RemoteJsonService;
|
||||||
@@ -28,6 +29,7 @@ public interface ChangeListService extends RemoteJsonService {
|
|||||||
*
|
*
|
||||||
* @param req the add and remove cluster.
|
* @param req the add and remove cluster.
|
||||||
*/
|
*/
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void toggleStars(ToggleStarRequest req, AsyncCallback<VoidResult> callback);
|
void toggleStars(ToggleStarRequest req, AsyncCallback<VoidResult> callback);
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.common.data;
|
package com.google.gerrit.common.data;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.audit.Audit;
|
||||||
import com.google.gerrit.common.auth.SignInRequired;
|
import com.google.gerrit.common.auth.SignInRequired;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||||
@@ -24,27 +25,34 @@ import com.google.gwtjsonrpc.common.RpcImpl.Version;
|
|||||||
|
|
||||||
@RpcImpl(version = Version.V2_0)
|
@RpcImpl(version = Version.V2_0)
|
||||||
public interface ChangeManageService extends RemoteJsonService {
|
public interface ChangeManageService extends RemoteJsonService {
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void submit(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
|
void submit(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void abandonChange(PatchSet.Id patchSetId, String message,
|
void abandonChange(PatchSet.Id patchSetId, String message,
|
||||||
AsyncCallback<ChangeDetail> callback);
|
AsyncCallback<ChangeDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void revertChange(PatchSet.Id patchSetId, String message,
|
void revertChange(PatchSet.Id patchSetId, String message,
|
||||||
AsyncCallback<ChangeDetail> callback);
|
AsyncCallback<ChangeDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void restoreChange(PatchSet.Id patchSetId, String message,
|
void restoreChange(PatchSet.Id patchSetId, String message,
|
||||||
AsyncCallback<ChangeDetail> callback);
|
AsyncCallback<ChangeDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void publish(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
|
void publish(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void deleteDraftChange(PatchSet.Id patchSetId, AsyncCallback<VoidResult> callback);
|
void deleteDraftChange(PatchSet.Id patchSetId, AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void rebaseChange(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
|
void rebaseChange(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.common.data;
|
package com.google.gerrit.common.data;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.audit.Audit;
|
||||||
import com.google.gerrit.common.auth.SignInRequired;
|
import com.google.gerrit.common.auth.SignInRequired;
|
||||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||||
import com.google.gerrit.reviewdb.client.AccountGroupInclude;
|
import com.google.gerrit.reviewdb.client.AccountGroupInclude;
|
||||||
@@ -28,48 +29,60 @@ import java.util.Set;
|
|||||||
|
|
||||||
@RpcImpl(version = Version.V2_0)
|
@RpcImpl(version = Version.V2_0)
|
||||||
public interface GroupAdminService extends RemoteJsonService {
|
public interface GroupAdminService extends RemoteJsonService {
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void visibleGroups(AsyncCallback<GroupList> callback);
|
void visibleGroups(AsyncCallback<GroupList> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void createGroup(String newName, AsyncCallback<AccountGroup.Id> callback);
|
void createGroup(String newName, AsyncCallback<AccountGroup.Id> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void groupDetail(AccountGroup.Id groupId, AccountGroup.UUID uuid,
|
void groupDetail(AccountGroup.Id groupId, AccountGroup.UUID uuid,
|
||||||
AsyncCallback<GroupDetail> callback);
|
AsyncCallback<GroupDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void changeGroupDescription(AccountGroup.Id groupId, String description,
|
void changeGroupDescription(AccountGroup.Id groupId, String description,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void changeGroupOptions(AccountGroup.Id groupId, GroupOptions groupOptions,
|
void changeGroupOptions(AccountGroup.Id groupId, GroupOptions groupOptions,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void changeGroupOwner(AccountGroup.Id groupId, String newOwnerName,
|
void changeGroupOwner(AccountGroup.Id groupId, String newOwnerName,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void renameGroup(AccountGroup.Id groupId, String newName,
|
void renameGroup(AccountGroup.Id groupId, String newName,
|
||||||
AsyncCallback<GroupDetail> callback);
|
AsyncCallback<GroupDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void changeGroupType(AccountGroup.Id groupId, AccountGroup.Type newType,
|
void changeGroupType(AccountGroup.Id groupId, AccountGroup.Type newType,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void addGroupMember(AccountGroup.Id groupId, String nameOrEmail,
|
void addGroupMember(AccountGroup.Id groupId, String nameOrEmail,
|
||||||
AsyncCallback<GroupDetail> callback);
|
AsyncCallback<GroupDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void addGroupInclude(AccountGroup.Id groupId, String groupName,
|
void addGroupInclude(AccountGroup.Id groupId, String groupName,
|
||||||
AsyncCallback<GroupDetail> callback);
|
AsyncCallback<GroupDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void deleteGroupMembers(AccountGroup.Id groupId,
|
void deleteGroupMembers(AccountGroup.Id groupId,
|
||||||
Set<AccountGroupMember.Key> keys, AsyncCallback<VoidResult> callback);
|
Set<AccountGroupMember.Key> keys, AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void deleteGroupIncludes(AccountGroup.Id groupId,
|
void deleteGroupIncludes(AccountGroup.Id groupId,
|
||||||
Set<AccountGroupInclude.Key> keys, AsyncCallback<VoidResult> callback);
|
Set<AccountGroupInclude.Key> keys, AsyncCallback<VoidResult> callback);
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.common.data;
|
package com.google.gerrit.common.data;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.audit.Audit;
|
||||||
import com.google.gerrit.common.auth.SignInRequired;
|
import com.google.gerrit.common.auth.SignInRequired;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
||||||
@@ -34,13 +35,16 @@ import java.util.Set;
|
|||||||
|
|
||||||
@RpcImpl(version = Version.V2_0)
|
@RpcImpl(version = Version.V2_0)
|
||||||
public interface PatchDetailService extends RemoteJsonService {
|
public interface PatchDetailService extends RemoteJsonService {
|
||||||
|
@Audit
|
||||||
void patchScript(Patch.Key key, PatchSet.Id a, PatchSet.Id b,
|
void patchScript(Patch.Key key, PatchSet.Id a, PatchSet.Id b,
|
||||||
AccountDiffPreference diffPrefs, AsyncCallback<PatchScript> callback);
|
AccountDiffPreference diffPrefs, AsyncCallback<PatchScript> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void saveDraft(PatchLineComment comment,
|
void saveDraft(PatchLineComment comment,
|
||||||
AsyncCallback<PatchLineComment> callback);
|
AsyncCallback<PatchLineComment> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void deleteDraft(PatchLineComment.Key key, AsyncCallback<VoidResult> callback);
|
void deleteDraft(PatchLineComment.Key key, AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
@@ -57,18 +61,22 @@ public interface PatchDetailService extends RemoteJsonService {
|
|||||||
* change, then <code>null</code> is passed as result to
|
* change, then <code>null</code> is passed as result to
|
||||||
* {@link AsyncCallback#onSuccess(Object)}
|
* {@link AsyncCallback#onSuccess(Object)}
|
||||||
*/
|
*/
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void deleteDraftPatchSet(PatchSet.Id psid, AsyncCallback<ChangeDetail> callback);
|
void deleteDraftPatchSet(PatchSet.Id psid, AsyncCallback<ChangeDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void publishComments(PatchSet.Id psid, String message,
|
void publishComments(PatchSet.Id psid, String message,
|
||||||
Set<ApprovalCategoryValue.Id> approvals,
|
Set<ApprovalCategoryValue.Id> approvals,
|
||||||
AsyncCallback<VoidResult> callback);
|
AsyncCallback<VoidResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void addReviewers(Change.Id id, List<String> reviewers, boolean confirmed,
|
void addReviewers(Change.Id id, List<String> reviewers, boolean confirmed,
|
||||||
AsyncCallback<ReviewerResult> callback);
|
AsyncCallback<ReviewerResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void removeReviewer(Change.Id id, Account.Id reviewerId,
|
void removeReviewer(Change.Id id, Account.Id reviewerId,
|
||||||
AsyncCallback<ReviewerResult> callback);
|
AsyncCallback<ReviewerResult> callback);
|
||||||
@@ -82,6 +90,7 @@ public interface PatchDetailService extends RemoteJsonService {
|
|||||||
/**
|
/**
|
||||||
* Update the reviewed status for the patch.
|
* Update the reviewed status for the patch.
|
||||||
*/
|
*/
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void setReviewedByCurrentUser(Key patchKey, boolean reviewed, AsyncCallback<VoidResult> callback);
|
void setReviewedByCurrentUser(Key patchKey, boolean reviewed, AsyncCallback<VoidResult> callback);
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.common.data;
|
package com.google.gerrit.common.data;
|
||||||
|
|
||||||
|
import com.google.gerrit.common.audit.Audit;
|
||||||
import com.google.gerrit.common.auth.SignInRequired;
|
import com.google.gerrit.common.auth.SignInRequired;
|
||||||
import com.google.gerrit.reviewdb.client.Branch;
|
import com.google.gerrit.reviewdb.client.Branch;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
@@ -34,6 +35,7 @@ public interface ProjectAdminService extends RemoteJsonService {
|
|||||||
void projectDetail(Project.NameKey projectName,
|
void projectDetail(Project.NameKey projectName,
|
||||||
AsyncCallback<ProjectDetail> callback);
|
AsyncCallback<ProjectDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void createNewProject(String projectName, String parentName,
|
void createNewProject(String projectName, String parentName,
|
||||||
boolean emptyCommit, boolean permissionsOnly,
|
boolean emptyCommit, boolean permissionsOnly,
|
||||||
@@ -42,10 +44,12 @@ public interface ProjectAdminService extends RemoteJsonService {
|
|||||||
void projectAccess(Project.NameKey projectName,
|
void projectAccess(Project.NameKey projectName,
|
||||||
AsyncCallback<ProjectAccess> callback);
|
AsyncCallback<ProjectAccess> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void changeProjectSettings(Project update,
|
void changeProjectSettings(Project update,
|
||||||
AsyncCallback<ProjectDetail> callback);
|
AsyncCallback<ProjectDetail> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void changeProjectAccess(Project.NameKey projectName, String baseRevision,
|
void changeProjectAccess(Project.NameKey projectName, String baseRevision,
|
||||||
String message, List<AccessSection> sections,
|
String message, List<AccessSection> sections,
|
||||||
@@ -59,10 +63,12 @@ public interface ProjectAdminService extends RemoteJsonService {
|
|||||||
void listBranches(Project.NameKey projectName,
|
void listBranches(Project.NameKey projectName,
|
||||||
AsyncCallback<ListBranchesResult> callback);
|
AsyncCallback<ListBranchesResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void addBranch(Project.NameKey projectName, String branchName,
|
void addBranch(Project.NameKey projectName, String branchName,
|
||||||
String startingRevision, AsyncCallback<ListBranchesResult> callback);
|
String startingRevision, AsyncCallback<ListBranchesResult> callback);
|
||||||
|
|
||||||
|
@Audit
|
||||||
@SignInRequired
|
@SignInRequired
|
||||||
void deleteBranch(Project.NameKey projectName, Set<Branch.NameKey> ids,
|
void deleteBranch(Project.NameKey projectName, Set<Branch.NameKey> ids,
|
||||||
AsyncCallback<Set<Branch.NameKey>> callback);
|
AsyncCallback<Set<Branch.NameKey>> callback);
|
||||||
|
@@ -14,7 +14,10 @@
|
|||||||
|
|
||||||
package com.google.gerrit.httpd;
|
package com.google.gerrit.httpd;
|
||||||
|
|
||||||
|
import com.google.gerrit.audit.AuditEvent;
|
||||||
|
import com.google.gerrit.audit.AuditService;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.account.AccountManager;
|
import com.google.gerrit.server.account.AccountManager;
|
||||||
import com.google.gerrit.server.config.AuthConfig;
|
import com.google.gerrit.server.config.AuthConfig;
|
||||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||||
@@ -36,19 +39,21 @@ class HttpLogoutServlet extends HttpServlet {
|
|||||||
private final Provider<WebSession> webSession;
|
private final Provider<WebSession> webSession;
|
||||||
private final Provider<String> urlProvider;
|
private final Provider<String> urlProvider;
|
||||||
private final String logoutUrl;
|
private final String logoutUrl;
|
||||||
|
private final AuditService audit;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
HttpLogoutServlet(final AuthConfig authConfig,
|
HttpLogoutServlet(final AuthConfig authConfig,
|
||||||
final Provider<WebSession> webSession,
|
final Provider<WebSession> webSession,
|
||||||
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
|
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
|
||||||
final AccountManager accountManager) {
|
final AccountManager accountManager,
|
||||||
|
final AuditService audit) {
|
||||||
this.webSession = webSession;
|
this.webSession = webSession;
|
||||||
this.urlProvider = urlProvider;
|
this.urlProvider = urlProvider;
|
||||||
this.logoutUrl = authConfig.getLogoutURL();
|
this.logoutUrl = authConfig.getLogoutURL();
|
||||||
|
this.audit = audit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void doLogout(final HttpServletRequest req,
|
||||||
protected void doGet(final HttpServletRequest req,
|
|
||||||
final HttpServletResponse rsp) throws IOException {
|
final HttpServletResponse rsp) throws IOException {
|
||||||
webSession.get().logout();
|
webSession.get().logout();
|
||||||
if (logoutUrl != null) {
|
if (logoutUrl != null) {
|
||||||
@@ -67,4 +72,22 @@ class HttpLogoutServlet extends HttpServlet {
|
|||||||
rsp.sendRedirect(url);
|
rsp.sendRedirect(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(final HttpServletRequest req,
|
||||||
|
final HttpServletResponse rsp) throws IOException {
|
||||||
|
|
||||||
|
final String sid = webSession.get().getToken();
|
||||||
|
final CurrentUser currentUser = webSession.get().getCurrentUser();
|
||||||
|
final String what = "sign out";
|
||||||
|
final long when = System.currentTimeMillis();
|
||||||
|
|
||||||
|
try {
|
||||||
|
doLogout(req, rsp);
|
||||||
|
} finally {
|
||||||
|
audit.dispatch(new AuditEvent(sid, currentUser,
|
||||||
|
what, when, null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -14,43 +14,71 @@
|
|||||||
|
|
||||||
package com.google.gerrit.httpd.rpc;
|
package com.google.gerrit.httpd.rpc;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.gerrit.audit.AuditEvent;
|
||||||
|
import com.google.gerrit.audit.AuditService;
|
||||||
|
import com.google.gerrit.common.audit.Audit;
|
||||||
import com.google.gerrit.common.auth.SignInRequired;
|
import com.google.gerrit.common.auth.SignInRequired;
|
||||||
import com.google.gerrit.common.errors.NotSignedInException;
|
import com.google.gerrit.common.errors.NotSignedInException;
|
||||||
import com.google.gerrit.httpd.WebSession;
|
import com.google.gerrit.httpd.WebSession;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gwtjsonrpc.common.RemoteJsonService;
|
import com.google.gwtjsonrpc.common.RemoteJsonService;
|
||||||
import com.google.gwtjsonrpc.server.ActiveCall;
|
import com.google.gwtjsonrpc.server.ActiveCall;
|
||||||
import com.google.gwtjsonrpc.server.JsonServlet;
|
import com.google.gwtjsonrpc.server.JsonServlet;
|
||||||
|
import com.google.gwtjsonrpc.server.MethodHandle;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base JSON servlet to ensure the current user is not forged.
|
* Base JSON servlet to ensure the current user is not forged.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall> {
|
final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall> {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GerritJsonServlet.class);
|
||||||
|
private static final ThreadLocal<GerritCall> currentCall =
|
||||||
|
new ThreadLocal<GerritCall>();
|
||||||
|
private static final ThreadLocal<MethodHandle> currentMethod =
|
||||||
|
new ThreadLocal<MethodHandle>();
|
||||||
private final Provider<WebSession> session;
|
private final Provider<WebSession> session;
|
||||||
private final RemoteJsonService service;
|
private final RemoteJsonService service;
|
||||||
|
private final AuditService audit;
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GerritJsonServlet(final Provider<WebSession> w, final RemoteJsonService s) {
|
GerritJsonServlet(final Provider<WebSession> w, final RemoteJsonService s,
|
||||||
|
final AuditService a) {
|
||||||
session = w;
|
session = w;
|
||||||
service = s;
|
service = s;
|
||||||
|
audit = a;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected GerritCall createActiveCall(final HttpServletRequest req,
|
protected GerritCall createActiveCall(final HttpServletRequest req,
|
||||||
final HttpServletResponse rsp) {
|
final HttpServletResponse rsp) {
|
||||||
return new GerritCall(session.get(), req, rsp);
|
final GerritCall call = new GerritCall(session.get(), req, rsp);
|
||||||
|
currentCall.set(call);
|
||||||
|
return call;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected GsonBuilder createGsonBuilder() {
|
protected GsonBuilder createGsonBuilder() {
|
||||||
final GsonBuilder g = super.createGsonBuilder();
|
return gerritDefaultGsonBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GsonBuilder gerritDefaultGsonBuilder() {
|
||||||
|
final GsonBuilder g = defaultGsonBuilder();
|
||||||
|
|
||||||
g.registerTypeAdapter(org.eclipse.jgit.diff.Edit.class,
|
g.registerTypeAdapter(org.eclipse.jgit.diff.Edit.class,
|
||||||
new org.eclipse.jgit.diff.EditDeserializer());
|
new org.eclipse.jgit.diff.EditDeserializer());
|
||||||
@@ -83,13 +111,113 @@ final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall>
|
|||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void service(final HttpServletRequest req,
|
||||||
|
final HttpServletResponse resp) throws IOException {
|
||||||
|
try {
|
||||||
|
super.service(req, resp);
|
||||||
|
} finally {
|
||||||
|
audit();
|
||||||
|
currentCall.set(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void audit() {
|
||||||
|
try {
|
||||||
|
GerritCall call = currentCall.get();
|
||||||
|
Audit note = (Audit) call.getMethod().getAnnotation(Audit.class);
|
||||||
|
if (note != null) {
|
||||||
|
final String sid = call.getWebSession().getToken();
|
||||||
|
final CurrentUser username = call.getWebSession().getCurrentUser();
|
||||||
|
final List<Object> args =
|
||||||
|
extractParams(note, call);
|
||||||
|
final String what = extractWhat(note, call.getMethod().getName());
|
||||||
|
final Object result = call.getResult();
|
||||||
|
|
||||||
|
audit.dispatch(new AuditEvent(sid, username, what, call.getWhen(), args,
|
||||||
|
result));
|
||||||
|
}
|
||||||
|
} catch (Throwable all) {
|
||||||
|
log.error("Unable to log the call", all);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Object> extractParams(final Audit note, final GerritCall call) {
|
||||||
|
List<Object> args = Lists.newArrayList(Arrays.asList(call.getParams()));
|
||||||
|
for (int idx : note.obfuscate()) {
|
||||||
|
args.set(idx, "*****");
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractWhat(final Audit note, final String methodName) {
|
||||||
|
String what = note.action();
|
||||||
|
if (what.length() == 0) {
|
||||||
|
boolean ccase = Character.isLowerCase(methodName.charAt(0));
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < methodName.length(); i++) {
|
||||||
|
char c = methodName.charAt(i);
|
||||||
|
if (ccase && !Character.isLowerCase(c)) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append(Character.toLowerCase(c));
|
||||||
|
}
|
||||||
|
what = sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return what;
|
||||||
|
}
|
||||||
|
|
||||||
static class GerritCall extends ActiveCall {
|
static class GerritCall extends ActiveCall {
|
||||||
private final WebSession session;
|
private final WebSession session;
|
||||||
|
private final long when;
|
||||||
|
private static final Field resultField;
|
||||||
|
|
||||||
|
// Needed to allow access to non-public result field in GWT/JSON-RPC
|
||||||
|
static {
|
||||||
|
Field declaredField = null;
|
||||||
|
try {
|
||||||
|
declaredField = ActiveCall.class.getDeclaredField("result");
|
||||||
|
declaredField.setAccessible(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Unable to expose RPS/JSON result field");
|
||||||
|
}
|
||||||
|
|
||||||
|
resultField = declaredField;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surrogate of the missing getResult() in GWT/JSON-RPC
|
||||||
|
public Object getResult() {
|
||||||
|
if (resultField == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return resultField.get(this);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.error("Cannot access result field");
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
log.error("No permissions to access result field");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
GerritCall(final WebSession session, final HttpServletRequest i,
|
GerritCall(final WebSession session, final HttpServletRequest i,
|
||||||
final HttpServletResponse o) {
|
final HttpServletResponse o) {
|
||||||
super(i, o);
|
super(i, o);
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
this.when = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodHandle getMethod() {
|
||||||
|
if (currentMethod.get() == null) {
|
||||||
|
return super.getMethod();
|
||||||
|
} else {
|
||||||
|
return currentMethod.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -120,5 +248,18 @@ final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall>
|
|||||||
return session.isSignedIn() && session.isTokenValid(keyIn);
|
return session.isSignedIn() && session.isTokenValid(keyIn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WebSession getWebSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getWhen() {
|
||||||
|
return when;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getElapsed() {
|
||||||
|
return System.currentTimeMillis() - when;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,172 @@
|
|||||||
|
// Copyright (C) 2012 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.audit;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AuditEvent {
|
||||||
|
|
||||||
|
public static final String UNKNOWN_SESSION_ID = "000000000000000000000000000";
|
||||||
|
private static final Object UNKNOWN_RESULT = "N/A";
|
||||||
|
|
||||||
|
public final String sessionId;
|
||||||
|
public final CurrentUser who;
|
||||||
|
public final long when;
|
||||||
|
public final String what;
|
||||||
|
public final List<?> params;
|
||||||
|
public final Object result;
|
||||||
|
public final long timeAtStart;
|
||||||
|
public final long elapsed;
|
||||||
|
public final UUID uuid;
|
||||||
|
|
||||||
|
public static class UUID {
|
||||||
|
|
||||||
|
protected final String uuid;
|
||||||
|
|
||||||
|
protected UUID() {
|
||||||
|
uuid = String.format("audit:%s", java.util.UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID(final String n) {
|
||||||
|
uuid = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return uuid.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof UUID)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuid.equals(((UUID) obj).uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new audit event.
|
||||||
|
*
|
||||||
|
* @param sessionId session id the event belongs to
|
||||||
|
* @param who principal that has generated the event
|
||||||
|
* @param what object of the event
|
||||||
|
* @param params parameters of the event
|
||||||
|
*/
|
||||||
|
public AuditEvent(String sessionId, CurrentUser who, String what, List<?> params) {
|
||||||
|
this(sessionId, who, what, System.currentTimeMillis(), params,
|
||||||
|
UNKNOWN_RESULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new audit event with results
|
||||||
|
*
|
||||||
|
* @param sessionId session id the event belongs to
|
||||||
|
* @param who principal that has generated the event
|
||||||
|
* @param what object of the event
|
||||||
|
* @param when time-stamp of when the event started
|
||||||
|
* @param params parameters of the event
|
||||||
|
* @param result result of the event
|
||||||
|
*/
|
||||||
|
public AuditEvent(String sessionId, CurrentUser who, String what, long when,
|
||||||
|
List<?> params, Object result) {
|
||||||
|
Preconditions.checkNotNull(what, "what is a mandatory not null param !");
|
||||||
|
|
||||||
|
this.sessionId = getValueWithDefault(sessionId, UNKNOWN_SESSION_ID);
|
||||||
|
this.who = who;
|
||||||
|
this.what = what;
|
||||||
|
this.when = when;
|
||||||
|
this.timeAtStart = this.when;
|
||||||
|
this.params = getValueWithDefault(params, Collections.emptyList());
|
||||||
|
this.uuid = new UUID();
|
||||||
|
this.result = result;
|
||||||
|
this.elapsed = System.currentTimeMillis() - timeAtStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T getValueWithDefault(T value, T defaultValueIfNull) {
|
||||||
|
if (value == null) {
|
||||||
|
return defaultValueIfNull;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return uuid.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (obj == null) return false;
|
||||||
|
if (getClass() != obj.getClass()) return false;
|
||||||
|
|
||||||
|
AuditEvent other = (AuditEvent) obj;
|
||||||
|
return this.uuid.equals(other.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(uuid.toString());
|
||||||
|
sb.append("|");
|
||||||
|
sb.append(sessionId);
|
||||||
|
sb.append('|');
|
||||||
|
sb.append(who);
|
||||||
|
sb.append('|');
|
||||||
|
sb.append(when);
|
||||||
|
sb.append('|');
|
||||||
|
sb.append(what);
|
||||||
|
sb.append('|');
|
||||||
|
sb.append(elapsed);
|
||||||
|
sb.append('|');
|
||||||
|
if (params != null) {
|
||||||
|
sb.append('[');
|
||||||
|
for (int i = 0; i < params.size(); i++) {
|
||||||
|
if (i > 0) sb.append(',');
|
||||||
|
|
||||||
|
Object param = params.get(i);
|
||||||
|
if (param == null) {
|
||||||
|
sb.append("null");
|
||||||
|
} else {
|
||||||
|
sb.append(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(']');
|
||||||
|
}
|
||||||
|
sb.append('|');
|
||||||
|
if (result != null) {
|
||||||
|
sb.append(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (C) 2012 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.audit;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.annotations.ExtensionPoint;
|
||||||
|
|
||||||
|
@ExtensionPoint
|
||||||
|
public interface AuditListener {
|
||||||
|
|
||||||
|
void onAuditableAction(AuditEvent action);
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (C) 2012 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.audit;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.registration.DynamicSet;
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
|
||||||
|
public class AuditModule extends AbstractModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
DynamicSet.setOf(binder(), AuditListener.class);
|
||||||
|
bind(AuditService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (C) 2012 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.audit;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.registration.DynamicSet;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class AuditService {
|
||||||
|
private final DynamicSet<AuditListener> auditListeners;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AuditService(DynamicSet<AuditListener> auditListeners) {
|
||||||
|
this.auditListeners = auditListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispatch(AuditEvent action) {
|
||||||
|
for (AuditListener auditListener : auditListeners) {
|
||||||
|
auditListener.onAuditableAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,6 +17,7 @@ package com.google.gerrit.server.config;
|
|||||||
import static com.google.inject.Scopes.SINGLETON;
|
import static com.google.inject.Scopes.SINGLETON;
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.gerrit.audit.AuditModule;
|
||||||
import com.google.gerrit.common.data.ApprovalTypes;
|
import com.google.gerrit.common.data.ApprovalTypes;
|
||||||
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
|
import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
|
||||||
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
|
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
|
||||||
@@ -167,6 +168,8 @@ public class GerritGlobalModule extends FactoryModule {
|
|||||||
bind(ProjectControl.GenericFactory.class);
|
bind(ProjectControl.GenericFactory.class);
|
||||||
factory(FunctionState.Factory.class);
|
factory(FunctionState.Factory.class);
|
||||||
|
|
||||||
|
install(new AuditModule());
|
||||||
|
|
||||||
bind(GitReferenceUpdated.class);
|
bind(GitReferenceUpdated.class);
|
||||||
DynamicMap.mapOf(binder(), new TypeLiteral<Cache<?, ?>>() {});
|
DynamicMap.mapOf(binder(), new TypeLiteral<Cache<?, ?>>() {});
|
||||||
DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
|
DynamicSet.setOf(binder(), GitReferenceUpdatedListener.class);
|
||||||
|
@@ -228,7 +228,7 @@ class CommandFactoryProvider implements Provider<CommandFactory> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Split a command line into a string array. */
|
/** Split a command line into a string array. */
|
||||||
static String[] split(String commandLine) {
|
static public String[] split(String commandLine) {
|
||||||
final List<String> list = new ArrayList<String>();
|
final List<String> list = new ArrayList<String>();
|
||||||
boolean inquote = false;
|
boolean inquote = false;
|
||||||
boolean inDblQuote = false;
|
boolean inDblQuote = false;
|
||||||
|
@@ -154,4 +154,8 @@ final class DispatchCommand extends BaseCommand {
|
|||||||
usage.append("\n");
|
usage.append("\n");
|
||||||
return usage.toString();
|
return usage.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCommandName() {
|
||||||
|
return commandName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,8 @@
|
|||||||
package com.google.gerrit.sshd;
|
package com.google.gerrit.sshd;
|
||||||
|
|
||||||
import com.google.gerrit.extensions.events.LifecycleListener;
|
import com.google.gerrit.extensions.events.LifecycleListener;
|
||||||
|
import com.google.gerrit.audit.AuditEvent;
|
||||||
|
import com.google.gerrit.audit.AuditService;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gerrit.server.PeerDaemonUser;
|
import com.google.gerrit.server.PeerDaemonUser;
|
||||||
@@ -40,6 +42,7 @@ import org.eclipse.jgit.util.QuotedString;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
@@ -58,12 +61,14 @@ class SshLog implements LifecycleListener {
|
|||||||
private final Provider<SshSession> session;
|
private final Provider<SshSession> session;
|
||||||
private final Provider<Context> context;
|
private final Provider<Context> context;
|
||||||
private final AsyncAppender async;
|
private final AsyncAppender async;
|
||||||
|
private final AuditService auditService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
SshLog(final Provider<SshSession> session, final Provider<Context> context,
|
SshLog(final Provider<SshSession> session, final Provider<Context> context,
|
||||||
final SitePaths site, @GerritServerConfig Config config) {
|
final SitePaths site, @GerritServerConfig Config config, AuditService auditService) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.auditService = auditService;
|
||||||
|
|
||||||
final DailyRollingFileAppender dst = new DailyRollingFileAppender();
|
final DailyRollingFileAppender dst = new DailyRollingFileAppender();
|
||||||
dst.setName(LOG_NAME);
|
dst.setName(LOG_NAME);
|
||||||
@@ -96,6 +101,7 @@ class SshLog implements LifecycleListener {
|
|||||||
|
|
||||||
void onLogin() {
|
void onLogin() {
|
||||||
async.append(log("LOGIN FROM " + session.get().getRemoteAddressAsString()));
|
async.append(log("LOGIN FROM " + session.get().getRemoteAddressAsString()));
|
||||||
|
audit("0", "LOGIN", new String[] {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void onAuthFail(final SshSession sd) {
|
void onAuthFail(final SshSession sd) {
|
||||||
@@ -121,6 +127,7 @@ class SshLog implements LifecycleListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async.append(event);
|
async.append(event);
|
||||||
|
audit("FAIL", "AUTH", new String[] {sd.getRemoteAddressAsString()});
|
||||||
}
|
}
|
||||||
|
|
||||||
void onExecute(int exitValue) {
|
void onExecute(int exitValue) {
|
||||||
@@ -158,10 +165,18 @@ class SshLog implements LifecycleListener {
|
|||||||
event.setProperty(P_STATUS, status);
|
event.setProperty(P_STATUS, status);
|
||||||
|
|
||||||
async.append(event);
|
async.append(event);
|
||||||
|
audit(status, getCommand(commandLine), CommandFactoryProvider.split(commandLine));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCommand(String commandLine) {
|
||||||
|
commandLine = commandLine.trim();
|
||||||
|
int spacePos = commandLine.indexOf(' ');
|
||||||
|
return (spacePos > 0 ? commandLine.substring(0, spacePos):commandLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onLogout() {
|
void onLogout() {
|
||||||
async.append(log("LOGOUT"));
|
async.append(log("LOGOUT"));
|
||||||
|
audit("0", "LOGOUT", new String[] {});
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoggingEvent log(final String msg) {
|
private LoggingEvent log(final String msg) {
|
||||||
@@ -192,7 +207,6 @@ class SshLog implements LifecycleListener {
|
|||||||
|
|
||||||
} else if (user instanceof PeerDaemonUser) {
|
} else if (user instanceof PeerDaemonUser) {
|
||||||
userName = PeerDaemonUser.USER_NAME;
|
userName = PeerDaemonUser.USER_NAME;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event.setProperty(P_USER_NAME, userName);
|
event.setProperty(P_USER_NAME, userName);
|
||||||
@@ -400,4 +414,44 @@ class SshLog implements LifecycleListener {
|
|||||||
public void setLogger(Logger logger) {
|
public void setLogger(Logger logger) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void audit(Object result, String commandName, String[] args) {
|
||||||
|
final Context ctx = context.get();
|
||||||
|
final String sid = extractSessionId(ctx);
|
||||||
|
final long created = extractCreated(ctx);
|
||||||
|
final String what = extractWhat(commandName, args);
|
||||||
|
auditService.dispatch(new AuditEvent(sid, extractCurrentUser(ctx), "ssh:"
|
||||||
|
+ what, created, Arrays.asList(args), result));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractWhat(String commandName, String[] args) {
|
||||||
|
String result = commandName;
|
||||||
|
if ("gerrit".equals(commandName)) {
|
||||||
|
if (args.length > 1)
|
||||||
|
result = "gerrit"+"."+args[1];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long extractCreated(final Context ctx) {
|
||||||
|
return (ctx != null) ? ctx.created : System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CurrentUser extractCurrentUser(final Context ctx) {
|
||||||
|
if (ctx != null) {
|
||||||
|
SshSession session = ctx.getSession();
|
||||||
|
return (session == null) ? null : session.getCurrentUser();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractSessionId(final Context ctx) {
|
||||||
|
if (ctx != null) {
|
||||||
|
SshSession session = ctx.getSession();
|
||||||
|
return (session == null) ? null : IdGenerator.format(session.getSessionId());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user