Skip to content

Commit

Permalink
🐛 fix refresh/reset/error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ebullient committed Jun 13, 2024
1 parent ea0b296 commit b600726
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public enum AdminDataCache {

COMMONHAUS_DATA(b -> b.expireAfterAccess(1, TimeUnit.HOURS)),

KNOWN_USER(b -> b.expireAfterWrite(2, TimeUnit.DAYS)),
KNOWN_USER(b -> b.expireAfterWrite(1, TimeUnit.DAYS)),

ALIASES(b -> b.expireAfterWrite(6, TimeUnit.HOURS)),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ public boolean updateFromPending() {
return this == UNKNOWN
|| this == PENDING;
}

public boolean couldBeActiveMember() {
return this != MemberStatus.REVOKED
&& this != MemberStatus.SUSPENDED
&& this != MemberStatus.DECLINED;
}
}

@SuppressWarnings("unused")
Expand Down Expand Up @@ -247,18 +253,48 @@ public void addHistory(String message) {
history.add("%s %s".formatted(when, message));
}

boolean updateMemberStatus(AppContextService ctx, Set<String> roles) {
MemberStatus oldStatus = data.status;
if (data.status == MemberStatus.UNKNOWN && !roles.isEmpty()) {
public boolean isMember() {
return data.status.couldBeActiveMember()
&& (isMember != null && isMember);
}

MemberStatus updateStatus(AppContextService ctx, Set<String> roles, MemberStatus oldStatus) {
MemberStatus newStatus = oldStatus;
if (isMember() && !roles.contains(MEMBER_ROLE)) {
// inconsistency: user is a member but does not have the member role
// this could be a missed automation step
ctx.logAndSendEmail("status",
"%s is a member but does not have the member role (missing group?)".formatted(login()),
null, null);
roles.add(MEMBER_ROLE);
}
if (newStatus == MemberStatus.UNKNOWN && !roles.isEmpty()) {
List<MemberStatus> status = roles.stream()
.map(r -> ctx.getStatusForRole(r))
.sorted()
.toList();
data.status = status.get(0);
newStatus = status.get(0);
}
return newStatus;
}

// read-only: test for change in status value
public boolean statusUpdateRequired(AppContextService ctx, Set<String> roles) {
MemberStatus newStatus = updateStatus(ctx, roles, data.status);
return data.status != newStatus;
}

// update user status
boolean updateMemberStatus(AppContextService ctx, Set<String> roles) {
MemberStatus oldStatus = data.status;
data.status = updateStatus(ctx, roles, data.status);
return oldStatus != data.status;
}

public void updateApplicationStatus(MemberSession session) {
session.applicationStatus(this.application != null);
}

public ApiResponse toResponse() {
return new ApiResponse(ApiResponse.Type.HAUS, data)
.responseStatus(postConflict() ? Response.Status.CONFLICT : Response.Status.OK);
Expand Down Expand Up @@ -327,8 +363,4 @@ public CommonhausUser build() {
return new CommonhausUser(this);
}
}

public void updateApplicationStatus(MemberSession session) {
session.applicationStatus(this.application != null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class MemberApplicationResource {
@Produces("application/json")
public Response getApplication() {
try {
CommonhausUser user = datastore.getCommonhausUser(session, false, false);
CommonhausUser user = datastore.getCommonhausUser(session);
if (user == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
Expand Down Expand Up @@ -77,7 +77,7 @@ public Response setApplication(ApplicationPost applicationPost) {
Log.errorf("setApplication|%s: No application data", session.login());
return Response.status(Response.Status.BAD_REQUEST).build();
}
CommonhausUser user = datastore.getCommonhausUser(session, false, false);
CommonhausUser user = datastore.getCommonhausUser(session);
if (user == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public Response updateAttestation(AttestationPost post) {
try {
final Set<String> roles = session.roles();
CommonhausUser user = datastore.getCommonhausUser(session, false, true);
// update status first so correct values are used for attestation
user.updateMemberStatus(ctx, roles);

Attestation newAttestation = createAttestation(user.status(), post);
Expand Down Expand Up @@ -83,6 +84,7 @@ public Response updateAttestations(List<AttestationPost> postList) {
try {
final Set<String> roles = session.roles();
CommonhausUser user = datastore.getCommonhausUser(session, false, true);
// update status first so correct values are used for attestation
user.updateMemberStatus(ctx, roles);

Map<String, Attestation> newAttestations = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import jakarta.ws.rs.core.NewCookie;
import jakarta.ws.rs.core.Response;

import org.commonhaus.automation.admin.AdminDataCache;
import org.commonhaus.automation.admin.github.AppContextService;
import org.commonhaus.automation.admin.github.CommonhausDatastore;
import org.commonhaus.automation.admin.github.CommonhausDatastore.UpdateEvent;
Expand Down Expand Up @@ -72,12 +71,7 @@ public Response finishLogin() {
@KnownUser
@Path("/me")
@Produces("application/json")
public Response getUserInfo(@DefaultValue("false") @QueryParam("refresh") boolean refresh) {
if (refresh) {
AdminDataCache.KNOWN_USER.invalidate(session.login());
session.userIsKnown(ctx);
}

public Response getUserInfo() {
if (session.hasConnection()) {
return Response.ok(new ApiResponse(ApiResponse.Type.INFO, session.getUserData())).build();
} else {
Expand All @@ -90,15 +84,12 @@ public Response getUserInfo(@DefaultValue("false") @QueryParam("refresh") boolea
@KnownUser
@Path("/commonhaus")
@Produces("application/json")
public Response getCommonhausUser(@DefaultValue("false") @QueryParam("refresh") boolean refresh) {
if (refresh) {
AdminDataCache.COMMONHAUS_DATA.invalidate(CommonhausDatastore.getKey(session));
}

public Response getCommonhausUser() {
try {
CommonhausUser user = datastore.getCommonhausUser(session, refresh, true);
CommonhausUser user = datastore.getCommonhausUser(session, false, true);
if (user == null) {
return Response.status(Response.Status.NOT_FOUND).build();
// This should not happen after a fetch with create=true
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
user.updateApplicationStatus(session);
return user.toResponse()
Expand All @@ -113,11 +104,20 @@ public Response getCommonhausUser(@DefaultValue("false") @QueryParam("refresh")
@KnownUser
@Path("/commonhaus/status")
@Produces("application/json")
public Response updateUserStatus() {
public Response updateUserStatus(@DefaultValue("false") @QueryParam("refresh") boolean refresh) {
if (refresh) {
// reset all the things.
session.forgetUser(ctx);

// re-fetch the user
session.userIsKnown(ctx);
Log.debugf("[%s] REFRESH /member/commonhaus/status %s", session.login(), session.roles());
}

try {
CommonhausUser user = datastore.getCommonhausUser(session, false, false);
CommonhausUser user = datastore.getCommonhausUser(session, refresh, false);
final Set<String> roles = session.roles();
if (user.updateMemberStatus(ctx, roles)) {
if (user.statusUpdateRequired(ctx, roles)) {
// Refresh the user's status
user = datastore.setCommonhausUser(new UpdateEvent(
user,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ private MemberSession(UserInfo userInfo) {
this.nodeId = userInfo.getString("node_id");
}

public void forgetUser(AppContextService ctx) {
ctx.forgetKnown(this);
}

public boolean userIsKnown(AppContextService ctx) {
UserQueryContext userQc = ctx.newUserQueryContext(this);
return ctx.userIsKnown(userQc, login(), roles());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import jakarta.inject.Inject;
import jakarta.json.JsonObject;

import org.commonhaus.automation.admin.AdminDataCache;
import org.commonhaus.automation.admin.api.ApplicationData;
import org.commonhaus.automation.admin.api.MemberApplicationProcess;
import org.commonhaus.automation.admin.config.UserManagementConfig.AttestationConfig;
Expand Down Expand Up @@ -72,7 +71,9 @@ public void updateMembership(GitHub github, DynamicGraphQLClient graphQLClient,
Log.debugf("[%s] updateMembership: %s",
installationId, eventPayload.getOrganization().getLogin());

AdminDataCache.KNOWN_USER.invalidate(eventPayload.getMember().getLogin());
// membership changed. Forget the user + roles
ctx.forgetKnown(eventPayload.getMember());

GHRepository repo = eventPayload.getRepository();
GHOrganization org = eventPayload.getOrganization();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,14 @@ public AttestationConfig attestationConfig() {
return userConfig.attestations();
}

public void forgetKnown(GHUser user) {
AdminDataCache.KNOWN_USER.invalidate(user.getLogin());
}

public void forgetKnown(MemberSession session) {
AdminDataCache.KNOWN_USER.invalidate(session.login());
}

public boolean userIsKnown(QueryContext initQc, String login, Set<String> roles) {
if (userConfig.isDisabled()) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,21 @@ public Uni<CommonhausUser> fetchCommonhausUser(QueryEvent event) {
GHRepository repo = qc.getRepository();
result = readCommonhausUser(qc, repo, event, key);
}

if (qc.clearNotFound() && event.create()) {
// create a new user
if (result == null && qc.clearNotFound() && event.create()) {
// create a new user if not found and no errors
result = CommonhausUser.create(event.login(), event.id());
}

// any other kind of error (including parse errors) will be logged and returned
if (qc.hasErrors()) {
Throwable e = qc.bundleExceptions();
qc.clearErrors();
Log.errorf(e, "[%s|%s] Unable to fetch user data for %s", qc.getLogId(), event.login(), e);
return Uni.createFrom().failure(e);
} else if (result == null && event.create()) {
Exception e = new IllegalStateException("No result for user after fetch with create");
ctx.logAndSendEmail(qc.getLogId(), "Failed to update Commonhaus user", e, null);
return Uni.createFrom().failure(e);
}

Log.debugf("[%s|%s] Fetched Commonhaus user data: %s", qc.getLogId(), event.login(), result);
Expand Down

0 comments on commit b600726

Please sign in to comment.