Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

b/277206647 Allow user to specify activation timeout #69

Merged
merged 3 commits into from
Apr 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,17 @@ public RoleActivationService(
public Activation activateProjectRoleForSelf(
UserId caller,
RoleBinding roleBinding,
String justification
String justification,
Duration activationTimeout
) throws AccessException, AlreadyExistsException, IOException {
Preconditions.checkNotNull(caller, "caller");
Preconditions.checkNotNull(roleBinding, "roleBinding");
Preconditions.checkNotNull(justification, "justification");
Preconditions.checkArgument(ProjectId.isProjectFullResourceName(roleBinding.fullResourceName));
Preconditions.checkArgument(activationTimeout.toMinutes() >= Options.MIN_ACTIVATION_TIMEOUT_MINUTES,
"The activation timeout is too short");
Preconditions.checkArgument(activationTimeout.toMinutes() <= this.options.maxActivationTimeout.toMinutes(),
"The requested activation timeout exceeds the maximum permitted timeout");

//
// Check that the justification looks reasonable.
Expand Down Expand Up @@ -156,7 +161,7 @@ public Activation activateProjectRoleForSelf(
//

var activationTime = Instant.now();
var expiryTime = activationTime.plus(this.options.activationDuration);
var expiryTime = activationTime.plus(activationTimeout);
var bindingDescription = String.format(
"Self-approved, justification: %s",
justification);
Expand Down Expand Up @@ -275,7 +280,8 @@ public ActivationRequest createActivationRequestForPeer(
UserId callerAndBeneficiary,
Set<UserId> reviewers,
RoleBinding roleBinding,
String justification
String justification,
Duration activationTimeout
) throws AccessException, IOException {
Preconditions.checkNotNull(callerAndBeneficiary, "callerAndBeneficiary");
Preconditions.checkNotNull(reviewers, "reviewers");
Expand All @@ -288,6 +294,10 @@ public ActivationRequest createActivationRequestForPeer(
Preconditions.checkArgument(reviewers.size() <= this.options.maxNumberOfReviewersPerActivationRequest,
"The number of reviewers must not exceed " + this.options.maxNumberOfReviewersPerActivationRequest);
Preconditions.checkArgument(!reviewers.contains(callerAndBeneficiary), "The beneficiary cannot be a reviewer");
Preconditions.checkArgument(activationTimeout.toMinutes() >= Options.MIN_ACTIVATION_TIMEOUT_MINUTES,
"The activation timeout is too short");
Preconditions.checkArgument(activationTimeout.toMinutes() <= this.options.maxActivationTimeout.toMinutes(),
"The requested activation timeout exceeds the maximum permitted timeout");

//
// Check that the justification looks reasonable.
Expand All @@ -307,7 +317,7 @@ public ActivationRequest createActivationRequestForPeer(
// Issue an activation request.
//
var startTime = Instant.now();
var endTime = startTime.plus(this.options.activationDuration);
var endTime = startTime.plus(activationTimeout);

return new ActivationRequest(
ActivationId.newId(ActivationType.MPA),
Expand Down Expand Up @@ -474,7 +484,9 @@ protected JsonWebToken.Payload toJsonWebTokenPayload() {
}

public static class Options {
public final Duration activationDuration;
public static final int MIN_ACTIVATION_TIMEOUT_MINUTES = 5;

public final Duration maxActivationTimeout;
public final String justificationHint;
public final Pattern justificationPattern;
public final int minNumberOfReviewersPerActivationRequest;
Expand All @@ -483,18 +495,21 @@ public static class Options {
public Options(
String justificationHint,
Pattern justificationPattern,
Duration activationDuration,
Duration maxActivationTimeout,
int minNumberOfReviewersPerActivationRequest,
int maxNumberOfReviewersPerActivationRequest)
{
Preconditions.checkArgument(
minNumberOfReviewersPerActivationRequest > 0,
"The minimum number of reviewers cannot be 0");
maxActivationTimeout.toMinutes() >= MIN_ACTIVATION_TIMEOUT_MINUTES,
"Activation timeout must be at least 5 minutes");
Preconditions.checkArgument(
minNumberOfReviewersPerActivationRequest > 0,
"The minimum number of reviewers cannot be 0");
Preconditions.checkArgument(
minNumberOfReviewersPerActivationRequest <= maxNumberOfReviewersPerActivationRequest,
"The minimum number of reviewers must not exceed the maximum");
minNumberOfReviewersPerActivationRequest <= maxNumberOfReviewersPerActivationRequest,
"The minimum number of reviewers must not exceed the maximum");

this.activationDuration = activationDuration;
this.maxActivationTimeout = maxActivationTimeout;
this.justificationHint = justificationHint;
this.justificationPattern = justificationPattern;
this.minNumberOfReviewersPerActivationRequest = minNumberOfReviewersPerActivationRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import javax.ws.rs.core.UriInfo;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
Expand Down Expand Up @@ -130,10 +131,12 @@ public PolicyResponse getPolicy(
) {
var iapPrincipal = (UserPrincipal) securityContext.getUserPrincipal();

var options = this.roleActivationService.getOptions();
return new PolicyResponse(
this.roleActivationService.getOptions().justificationHint,
iapPrincipal.getId()
);
options.justificationHint,
iapPrincipal.getId(),
(int)options.maxActivationTimeout.toMinutes(),
Math.min(60, (int)options.maxActivationTimeout.toMinutes()));
}

/**
Expand Down Expand Up @@ -296,7 +299,8 @@ public ActivationStatusResponse selfApproveActivation(
var activation = this.roleActivationService.activateProjectRoleForSelf(
iapPrincipal.getId(),
roleBinding,
request.justification);
request.justification,
Duration.ofMinutes(request.activationTimeout));

assert activation != null;
activations.add(activation);
Expand Down Expand Up @@ -406,7 +410,8 @@ public ActivationStatusResponse requestActivation(
iapPrincipal.getId(),
request.peers.stream().map(email -> new UserId(email)).collect(Collectors.toSet()),
roleBinding,
request.justification);
request.justification,
Duration.ofMinutes(request.activationTimeout));

//
// Create an approval token and pass it to reviewers.
Expand Down Expand Up @@ -660,16 +665,25 @@ private static LogAdapter.LogEntry addLabels(
public static class PolicyResponse {
public final String justificationHint;
public final UserId signedInUser;
public final int defaultActivationTimeout; // in minutes.
public final int maxActivationTimeout; // in minutes.

private PolicyResponse(
String justificationHint,
UserId signedInUser
UserId signedInUser,
int maxActivationTimeoutInMinutes,
int defaultActivationTimeoutInMinutes
) {
Preconditions.checkNotNull(justificationHint, "justificationHint");
Preconditions.checkNotNull(signedInUser, "signedInUser");
Preconditions.checkArgument(defaultActivationTimeoutInMinutes > 0, "defaultActivationTimeoutInMinutes");
Preconditions.checkArgument(maxActivationTimeoutInMinutes > 0, "maxActivationTimeoutInMinutes");
Preconditions.checkArgument(maxActivationTimeoutInMinutes >= defaultActivationTimeoutInMinutes, "maxActivationTimeoutInMinutes");

this.justificationHint = justificationHint;
this.signedInUser = signedInUser;
this.defaultActivationTimeout = defaultActivationTimeoutInMinutes;
this.maxActivationTimeout = maxActivationTimeoutInMinutes;
}
}

Expand Down Expand Up @@ -709,12 +723,14 @@ private ProjectRolePeersResponse(Set<UserId> peers) {
public static class SelfActivationRequest {
public List<String> roles;
public String justification;
public int activationTimeout; // in minutes.
}

public static class ActivationRequest {
public String role;
public String justification;
public List<String> peers;
public int activationTimeout; // in minutes.
}

public static class ActivationStatusResponse {
Expand Down
33 changes: 31 additions & 2 deletions sources/src/main/resources/META-INF/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@
</tbody>
</table>
</div>
<div class="step-subsection">
Duration: <span id="selected-activation-timeout"></span> minutes.
<input class="mdl-slider mdl-js-slider" id="activation-timeout" type="range" min="5" max="10" value="5" step="5" tabindex="0">
</div>
</div>
<div class="mdl-step__actions">
<button class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--colored mdl-button--raised" data-stepper-next>
Expand Down Expand Up @@ -534,6 +538,10 @@ <h2 class="mdl-card__title-text">Audit and review just-in-time access</h2>
this.approveStep = new MdlStep("#step-approve");
this._roleTable = new MdlDataTable("#step-selectrole-roles");
this._peersTable = new MdlDataTable("#step-approve-peers");

$("#activation-timeout").change(() => {
$("#selected-activation-timeout").text(this.activationTimeout);
});
}

get selectedProjectId() {
Expand All @@ -552,6 +560,23 @@ <h2 class="mdl-card__title-text">Audit and review just-in-time access</h2>
$("#step-approve legend").text(hint);
}

get activationTimeout() {
return $("#activation-timeout")[0].value;
}

set activationTimeout(timeoutInMinutes) {
$("#activation-timeout")[0].MaterialSlider.change(timeoutInMinutes);
$("#selected-activation-timeout").text(timeoutInMinutes);
}

get maxActivationTimeout() {
return $("#activation-timeout")[0].max;
}

set maxActivationTimeout(timeoutInMinutes) {
$("#activation-timeout")[0].max = timeoutInMinutes;
}

set roles(roles) {
this._roles = roles;

Expand Down Expand Up @@ -744,6 +769,8 @@ <h2 class="mdl-card__title-text">Audit and review just-in-time access</h2>
}

requestPane.roles = rolesResult.roles;
requestPane.maxActivationTimeout = backend.policy.maxActivationTimeout;
requestPane.activationTimeout = backend.policy.defaultActivationTimeout;
});

//
Expand Down Expand Up @@ -806,13 +833,15 @@ <h2 class="mdl-card__title-text">Audit and review just-in-time access</h2>
requestPane.selectedProjectId,
requestPane.selectedRoles[0].roleBinding.role,
requestPane.selectedPeers,
requestPane.justificationText);
requestPane.justificationText,
parseInt(requestPane.activationTimeout));
}
else {
activationStatus = await backend.selfApproveActivation(
requestPane.selectedProjectId,
requestPane.selectedRoles.map(r => r.roleBinding.role),
requestPane.justificationText)
requestPane.justificationText,
parseInt(requestPane.activationTimeout))
}

gui.requestStatusPane.activationStatus = activationStatus;
Expand Down
40 changes: 25 additions & 15 deletions sources/src/main/resources/META-INF/resources/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,11 @@ class Model {
}

/** Activate roles without peer approval */
async selfApproveActivation(projectId, roles, justification) {
async selfApproveActivation(projectId, roles, justification, activationTimeout) {
console.assert(projectId);
console.assert(roles.length > 0);
console.assert(justification)
console.assert(activationTimeout)

try {
return await $.ajax({
Expand All @@ -126,7 +127,8 @@ class Model {
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
roles: roles,
justification: justification
justification: justification,
activationTimeout: activationTimeout
}),
headers: this._getHeaders()
});
Expand All @@ -137,11 +139,12 @@ class Model {
}

/** Activate a role with peer approval */
async requestActivation(projectId, role, peers, justification) {
async requestActivation(projectId, role, peers, justification, activationTimeout) {
console.assert(projectId);
console.assert(role);
console.assert(peers.length > 0);
console.assert(justification)
console.assert(activationTimeout)

try {
return await $.ajax({
Expand All @@ -152,7 +155,8 @@ class Model {
data: JSON.stringify({
role: role,
justification: justification,
peers: peers
peers: peers,
activationTimeout: activationTimeout
}),
headers: this._getHeaders()
});
Expand Down Expand Up @@ -304,7 +308,7 @@ class DebugModel extends Model {
return Promise.reject("Simulated error");
}

async _simulateActivationResponse(projectId, justification, roles, status, forSelf) {
async _simulateActivationResponse(projectId, justification, roles, status, forSelf, activationTimeout) {
await new Promise(r => setTimeout(r, 1000));
return Promise.resolve({
isBeneficiary: forSelf,
Expand All @@ -321,7 +325,7 @@ class DebugModel extends Model {
},
status: status,
startTime: Math.floor(Date.now() / 1000),
endTime: Math.floor(Date.now() / 1000) + 300
endTime: Math.floor(Date.now() / 1000) + activationTimeout * 60
}))
});
}
Expand All @@ -331,7 +335,9 @@ class DebugModel extends Model {
justificationHint: "simulated hint",
signedInUser: {
email: "user@example.com"
}
},
defaultActivationTimeout: 60,
maxActivationTimeout: 120
};
}

Expand Down Expand Up @@ -395,10 +401,10 @@ class DebugModel extends Model {
}
}

async selfApproveActivation(projectId, roles, justification) {
async selfApproveActivation(projectId, roles, justification, activationTimeout) {
var setting = $("#debug-selfApproveActivation").val();
if (!setting) {
return super.selfApproveActivation(projectId, roles, justification);
return super.selfApproveActivation(projectId, roles, justification, activationTimeout);
}
else if (setting === "error") {
await this._simulateError();
Expand All @@ -409,14 +415,15 @@ class DebugModel extends Model {
justification,
roles,
"ACTIVATED",
true);
true,
activationTimeout);
}
}

async requestActivation(projectId, role, peers, justification) {
async requestActivation(projectId, role, peers, justification, activationTimeout) {
var setting = $("#debug-requestActivation").val();
if (!setting) {
return super.requestActivation(projectId, role, peers, justification);
return super.requestActivation(projectId, role, peers, justification, activationTimeout);
}
else if (setting === "error") {
await this._simulateError();
Expand All @@ -427,7 +434,8 @@ class DebugModel extends Model {
justification,
[role],
"ACTIVATION_PENDING",
true);
true,
activationTimeout);
}
}

Expand All @@ -445,7 +453,8 @@ class DebugModel extends Model {
"a justification",
["roles/role-1"],
"ACTIVATION_PENDING",
false);
false,
60);
}
}

Expand All @@ -463,7 +472,8 @@ class DebugModel extends Model {
"a justification",
["roles/role-1"],
"ACTIVATED",
false);
false,
60);
}
}
}
Loading