/
Permission.java
163 lines (156 loc) · 6.89 KB
/
Permission.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package serverx.utils;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.mongo.UpdateOptions;
import io.vertx.ext.web.RoutingContext;
import serverx.route.RouteInfo;
import serverx.server.ServerxVerticle;
/**
* Permission utilities.
*/
public class Permission {
/**
* Grant or revoke a permission.
*
* @param ctx
* the ctx
* @param email
* the email
* @param permission
* the permission
* @param grant
* whether to grant (true) or revoke (false)
* @param future
* the future
*/
private static void grantOrRevoke(final RoutingContext ctx, final String email, final String permission,
final boolean grant, final Future<?> future) {
ServerxVerticle.mongoClient.updateCollectionWithOptions(RouteInfo.PERMISSIONS_COLLECTION_NAME,
new JsonObject().put("_id", email),
new JsonObject().put(grant ? "$set" : "$unset",
new JsonObject().put(RouteInfo.PERMISSIONS_SESSION_PROPERTY_KEY + "." + permission,
grant ? true : 1)),
new UpdateOptions().setUpsert(true), //
ar -> {
if (ar.succeeded()) {
if (ar.result().getDocModified() == 0) {
// Didn't change anything in the database
if (ar.result().getDocMatched() == 1) {
// But matched one record based on email address, so permission was already
// granted or revoked for user
future.fail("User " + email + (grant ? " already has" : " does not have")
+ " permission " + permission);
} else {
// Otherwise did not match email address in the database
future.fail("Failed to " + (grant ? "grant" : "revoke") + " permission "
+ permission + " for user " + email);
}
} else {
// Successfully granted or revoked permission in database.
// If granting permission, also add to session cache.
if (ctx != null && grant) {
var permissions = (JsonObject) ctx.session()
.get(RouteInfo.PERMISSIONS_SESSION_PROPERTY_KEY);
if (permissions == null) {
// Create a new permissions object in the session.
// N.B. there's a race condition here, since putIfAbsent isn't supported:
// https://github.com/vert-x3/vertx-web/issues/1205
// This could cause a permission to get lost, if multiple threads are
// trying to add permissions (which is probably vanishingly rare).
permissions = new JsonObject();
ctx.session().put(RouteInfo.PERMISSIONS_SESSION_PROPERTY_KEY, permissions);
}
// Add permission to session cache
permissions.put(permission, Boolean.TRUE);
}
future.complete();
}
} else {
future.fail(ar.cause());
}
});
}
/**
* Grant or revoke a permission.
*
* @param ctx
* the ctx
* @param permission
* the permission
* @param grant
* whether to grant (true) or revoke (false)
* @return the future
*/
private static Future<?> grantOrRevoke(final RoutingContext ctx, final String permission, final boolean grant) {
final var future = Future.future();
if (ctx != null && !grant) {
// If revoking, revoke permission immediately from session cache for safety.
// (if granting, only add grant to session later after permission has been updated in database)
final var permissions = (JsonObject) ctx.session().get(RouteInfo.PERMISSIONS_SESSION_PROPERTY_KEY);
if (permissions != null) {
// Remove cached permission, if present
permissions.remove(permission);
}
}
// Look up email address, since this is the key for the permissions collection in the database
final var email = (String) ctx.session().get(RouteInfo.EMAIL_SESSION_PROPERTY_KEY);
if (email == null) {
future.fail("email property not found in session");
} else {
grantOrRevoke(ctx, email, permission, grant, future);
}
return future;
}
/**
* Revoke the named permission from the current user.
*
* @param ctx
* the ctx
* @param permission
* the permission
* @return the future
*/
public static Future<?> revoke(final RoutingContext ctx, final String permission) {
return grantOrRevoke(ctx, permission, /* grant = */ false);
}
/**
* Grant the named permission to the current user.
*
* @param ctx
* the ctx
* @param permission
* the permission
* @return the future
*/
public static Future<?> grant(final RoutingContext ctx, final String permission) {
return grantOrRevoke(ctx, permission, /* grant = */ true);
}
/**
* Revoke the named permission from the named user. Will not update the session, since sessions are keyed by
* session id rather than email address, so the user will need to log out and log back in for the changes to
* take effect. This is a security risk, since revocation is not immediate.
*
* @param email
* the email
* @param permission
* the permission
* @return the future
*/
public static Future<?> revoke(final String email, final String permission) {
return grantOrRevoke(/* ctx = */ null, permission, /* grant = */ false);
}
/**
* Grant the named permission to the named user. Will not update the session, since sessions are keyed by
* session id rather than email address, so the user will need to log out and log back in for the changes to
* take effect.
*
* @param email
* the email
* @param permission
* the permission
* @return the future
*/
public static Future<?> grant(final String email, final String permission) {
return grantOrRevoke(/* ctx = */ null, permission, /* grant = */ true);
}
}