-
Notifications
You must be signed in to change notification settings - Fork 18
Feature/1092 1098 basic user management #1115
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
Changes from all commits
34ba0f5
8cb8a91
db6df3e
6e544de
4971c5b
42cd77a
76f98d8
a47888e
9c42037
c2984ca
8871714
1535305
b551974
931899c
12f0d9d
6c70118
507b115
68cd749
313eb39
3437548
7e811ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package cwms.cda.api.auth.users; | ||
|
||
import static cwms.cda.api.Controllers.STATUS_200; | ||
import static cwms.cda.data.dao.JooqDao.getDslContext; | ||
|
||
import java.security.Principal; | ||
import java.util.Optional; | ||
|
||
import org.jooq.DSLContext; | ||
|
||
import com.codahale.metrics.MetricRegistry; | ||
|
||
import cwms.cda.ApiServlet; | ||
import cwms.cda.data.dao.AuthDao; | ||
import cwms.cda.data.dao.UserDao; | ||
import cwms.cda.data.dto.Clobs; | ||
import cwms.cda.data.dto.auth.users.User; | ||
import cwms.cda.formatters.ContentType; | ||
import cwms.cda.formatters.Formats; | ||
import cwms.cda.security.DataApiPrincipal; | ||
import cwms.cda.security.Role; | ||
import io.javalin.core.util.Header; | ||
import io.javalin.http.Context; | ||
import io.javalin.http.Handler; | ||
import io.javalin.plugin.openapi.annotations.HttpMethod; | ||
import io.javalin.plugin.openapi.annotations.OpenApi; | ||
import io.javalin.plugin.openapi.annotations.OpenApiContent; | ||
import io.javalin.plugin.openapi.annotations.OpenApiParam; | ||
import io.javalin.plugin.openapi.annotations.OpenApiResponse; | ||
import io.javalin.plugin.openapi.annotations.OpenApiSecurity; | ||
|
||
public class UserProfileController implements Handler { | ||
|
||
private final MetricRegistry metrics; | ||
|
||
public UserProfileController(MetricRegistry metrics) { | ||
this.metrics = metrics; | ||
} | ||
|
||
@OpenApi( | ||
responses = @OpenApiResponse( | ||
content = { | ||
@OpenApiContent(from = User.class, type = Formats.JSON) | ||
}, | ||
status = STATUS_200 | ||
), | ||
security = { | ||
@OpenApiSecurity(name = "gets overridden allows lock icon.") | ||
}, | ||
description = "View users' own information", | ||
method = HttpMethod.GET, | ||
tags = {"User Management"} | ||
) | ||
@Override | ||
public void handle(Context ctx) throws Exception { | ||
DataApiPrincipal p = ctx.attribute(AuthDao.DATA_API_PRINCIPAL); | ||
DSLContext dsl = getDslContext(ctx); | ||
UserDao dao = new UserDao(dsl); | ||
String cac_user = p.getRoles() | ||
.stream() | ||
.filter(r -> r.equals(new Role(ApiServlet.CAC_USER))) | ||
.map(r -> ApiServlet.CAC_USER) | ||
.findFirst().orElse(null); | ||
User user = dao.getByUniqueName(p.getName(), cac_user).orElse(null); | ||
String formatHeader = ctx.header(Header.ACCEPT); | ||
ContentType contentType = Formats.parseHeader(formatHeader, User.class); | ||
String result = Formats.format(contentType, user); | ||
|
||
ctx.result(result); | ||
ctx.contentType(contentType.toString()); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package cwms.cda.api.auth.users; | ||
|
||
import static com.codahale.metrics.MetricRegistry.name; | ||
import static cwms.cda.api.Controllers.CURSOR; | ||
import static cwms.cda.api.Controllers.GET_ALL; | ||
import static cwms.cda.api.Controllers.INCLUDE_VALUES; | ||
import static cwms.cda.api.Controllers.OFFICE; | ||
import static cwms.cda.api.Controllers.PAGE; | ||
import static cwms.cda.api.Controllers.PAGE_SIZE; | ||
import static cwms.cda.api.Controllers.STATUS_200; | ||
import static cwms.cda.api.Controllers.STATUS_201; | ||
import static cwms.cda.api.Controllers.STATUS_204; | ||
import static cwms.cda.api.Controllers.markAndTime; | ||
import static cwms.cda.api.Controllers.queryParamAsClass; | ||
import static cwms.cda.data.dao.JooqDao.getDslContext; | ||
|
||
import java.util.List; | ||
|
||
import org.jooq.DSLContext; | ||
|
||
import com.codahale.metrics.MetricRegistry; | ||
import com.codahale.metrics.Timer; | ||
|
||
import cwms.cda.ApiServlet; | ||
import cwms.cda.api.ClobController; | ||
import cwms.cda.api.Controllers; | ||
import cwms.cda.api.errors.CdaError; | ||
import cwms.cda.data.dao.UserDao; | ||
import cwms.cda.data.dto.Clobs; | ||
import cwms.cda.data.dto.CwmsDTOPaginated; | ||
import cwms.cda.data.dto.auth.ApiKey; | ||
import cwms.cda.data.dto.auth.users.User; | ||
import cwms.cda.data.dto.auth.users.Users; | ||
import cwms.cda.formatters.ContentType; | ||
import cwms.cda.formatters.Formats; | ||
import cwms.cda.security.Role; | ||
import io.javalin.apibuilder.CrudHandler; | ||
import io.javalin.core.security.RouteRole; | ||
import io.javalin.core.util.Header; | ||
import io.javalin.http.Context; | ||
import io.javalin.http.HttpCode; | ||
import io.javalin.plugin.openapi.annotations.OpenApi; | ||
import io.javalin.plugin.openapi.annotations.OpenApiContent; | ||
import io.javalin.plugin.openapi.annotations.OpenApiParam; | ||
import io.javalin.plugin.openapi.annotations.OpenApiRequestBody; | ||
import io.javalin.plugin.openapi.annotations.OpenApiResponse; | ||
import io.javalin.plugin.openapi.annotations.OpenApiSecurity; | ||
|
||
public class UsersController implements CrudHandler { | ||
private final MetricRegistry metrics; | ||
private static final int DEFAULT_PAGE_SIZE = 100; | ||
public static final String TAG = "User Management"; | ||
|
||
public UsersController(MetricRegistry metrics) { | ||
this.metrics = metrics; | ||
|
||
} | ||
|
||
private Timer.Context markAndTime(String subject) { | ||
return Controllers.markAndTime(metrics, getClass().getName(), subject); | ||
} | ||
|
||
@OpenApi(ignore = true) | ||
@Override | ||
public void create(Context ctx) { | ||
throw new UnsupportedOperationException("Unimplemented method 'create'"); | ||
} | ||
|
||
@OpenApi(ignore = true) | ||
@Override | ||
public void delete(Context ctx, String username) { | ||
// TODO Auto-generated method stub | ||
throw new UnsupportedOperationException("Unimplemented method 'delete'"); | ||
} | ||
|
||
|
||
@OpenApi( | ||
queryParams = { | ||
@OpenApiParam(allowEmptyValue = true, name = OFFICE, type = String.class, | ||
description = "Show only users with active privileges in a given office." | ||
+ Controllers.OFFICE_DESCRIPTION ), | ||
@OpenApiParam(name = PAGE, | ||
description = "This end point can return a lot of data, this " | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't know if this needs to be said, but I get the spirit of it "Lots of data, don't forget about the page option" |
||
+ "identifies where in the request you are. This is an opaque" | ||
+ " value, and can be obtained from the 'next-page' value in " | ||
+ "the response."), | ||
@OpenApiParam(name = PAGE_SIZE, | ||
type = Integer.class, | ||
description = "How many entries per page returned. Default " | ||
+ DEFAULT_PAGE_SIZE + ".") | ||
}, | ||
responses = @OpenApiResponse( | ||
content = { | ||
@OpenApiContent(from = Users.class, type = Formats.JSON) | ||
}, | ||
status = STATUS_200 | ||
), | ||
security = { | ||
@OpenApiSecurity(name = "gets overridden allows lock icon.") | ||
}, | ||
description = "View all users", | ||
tags = {TAG} | ||
) | ||
@Override | ||
public void getAll(Context ctx) { | ||
try (final Timer.Context ignored = markAndTime(GET_ALL)) { | ||
DSLContext dsl = getDslContext(ctx); | ||
String office = ctx.queryParam(OFFICE); | ||
|
||
String formatHeader = ctx.header(Header.ACCEPT); | ||
ContentType contentType = Formats.parseHeader(formatHeader, Users.class); | ||
|
||
String cursor = queryParamAsClass(ctx, new String[]{PAGE, CURSOR}, | ||
String.class, "", metrics, name(UsersController.class.getName(), GET_ALL)); | ||
|
||
if (!CwmsDTOPaginated.CURSOR_CHECK.invoke(cursor)) { | ||
ctx.json(new CdaError("cursor or page passed in but failed validation")) | ||
.status(HttpCode.BAD_REQUEST); | ||
return; | ||
} | ||
|
||
int pageSize = queryParamAsClass(ctx, new String[]{PAGE_SIZE}, Integer.class, DEFAULT_PAGE_SIZE, metrics, | ||
name(UsersController.class.getName(), GET_ALL)); | ||
|
||
boolean includeRoles = queryParamAsClass(ctx, new String[]{"include-roles"}, | ||
Boolean.class, false, metrics, | ||
name(UsersController.class.getName(), GET_ALL)); | ||
UserDao dao = new UserDao(dsl); | ||
Users users = dao.getAll(cursor, pageSize, office, includeRoles); | ||
|
||
String result = Formats.format(contentType, users); | ||
|
||
ctx.result(result); | ||
ctx.contentType(contentType.toString()); | ||
} | ||
} | ||
|
||
@OpenApi( | ||
pathParams = { | ||
@OpenApiParam(name = "user-name", required = true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is the question. |
||
description = "Specific user to retrieve") | ||
}, | ||
responses = @OpenApiResponse( | ||
content = { | ||
@OpenApiContent(from = User.class, type = Formats.JSON) | ||
}, | ||
status = STATUS_200 | ||
), | ||
security = { | ||
@OpenApiSecurity(name = "gets overridden allows lock icon.") | ||
}, | ||
description = "View specific user", | ||
tags = {TAG} | ||
) | ||
@Override | ||
public void getOne(Context ctx, String userName) { | ||
DSLContext dsl = getDslContext(ctx); | ||
UserDao dao = new UserDao(dsl); | ||
User user = dao.getByUniqueName(userName, null).orElse(null); | ||
String formatHeader = ctx.header(Header.ACCEPT); | ||
ContentType contentType = Formats.parseHeader(formatHeader, User.class); | ||
String result = Formats.format(contentType, user); | ||
|
||
ctx.result(result); | ||
ctx.contentType(contentType.toString()); | ||
} | ||
|
||
@OpenApi( | ||
ignore = true // users cannot be updated. Rolls are handled by a separate endpoint. | ||
) | ||
@Override | ||
public void update(Context ctx, String arg1) { | ||
throw new UnsupportedOperationException("Unimplemented method 'update'"); | ||
} | ||
|
||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package cwms.cda.api.auth.users.roles; | ||
|
||
import static cwms.cda.api.Controllers.STATUS_204; | ||
import static cwms.cda.data.dao.JooqDao.getDslContext; | ||
|
||
import com.codahale.metrics.MetricRegistry; | ||
|
||
import cwms.cda.api.Controllers; | ||
import cwms.cda.data.dao.AuthDao; | ||
import cwms.cda.data.dao.UserDao; | ||
import cwms.cda.formatters.Formats; | ||
import cwms.cda.security.DataApiPrincipal; | ||
import io.javalin.http.Context; | ||
import io.javalin.http.Handler; | ||
import io.javalin.http.HttpCode; | ||
import io.javalin.plugin.openapi.annotations.OpenApi; | ||
import io.javalin.plugin.openapi.annotations.OpenApiContent; | ||
import io.javalin.plugin.openapi.annotations.OpenApiParam; | ||
import io.javalin.plugin.openapi.annotations.OpenApiRequestBody; | ||
import io.javalin.plugin.openapi.annotations.OpenApiResponse; | ||
import io.javalin.plugin.openapi.annotations.OpenApiSecurity; | ||
|
||
public class AddRoleController implements Handler { | ||
private final MetricRegistry metrics; | ||
|
||
public AddRoleController(MetricRegistry metrics) { | ||
this.metrics = metrics; | ||
} | ||
|
||
@OpenApi( | ||
pathParams = { | ||
@OpenApiParam(name = "office-id", required = true, | ||
description = "Office for these roles." + Controllers.OFFICE_DESCRIPTION), | ||
@OpenApiParam(name = "user-name", required = true, | ||
description = "Name of the user to alter") | ||
}, | ||
responses = @OpenApiResponse( | ||
status = STATUS_204 | ||
), | ||
requestBody = @OpenApiRequestBody( | ||
content = { | ||
@OpenApiContent(from = String[].class, type = Formats.JSON, isArray = true) | ||
} | ||
), | ||
security = { | ||
@OpenApiSecurity(name = "gets overridden allows lock icon.") | ||
}, | ||
description = "Add roles to user", | ||
tags = {"User Management"} | ||
) | ||
@Override | ||
public void handle(Context ctx) throws Exception { | ||
final DataApiPrincipal p = ctx.attribute(AuthDao.DATA_API_PRINCIPAL); | ||
final String user = ctx.pathParam("user-name"); | ||
final String office = ctx.pathParam("office-id"); | ||
final String[] roles = ctx.bodyAsClass(String[].class); | ||
UserDao dao = new UserDao(getDslContext(ctx)); | ||
dao.addRoles(p, user, office, roles); | ||
ctx.status(HttpCode.NO_CONTENT); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this suggest that a user could have a space in multiple offices?
As opposed to
/user/{office-id}/roles/{user-name}
?I would just think that office would be the base and a user would be requested from that office
This probably doesn't really matter since either way the values get passed in. It's just, to me an office seems like it would come first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For most of the other data you would be correct, but in this case I don't think so.
The user is a the root of the infrastructure, and permissions to an office data are secondary.
E.g. the user "belongs" to the system as a whole.
Versus say a Time Series which "belongs" to a specific office.