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

Session Delete Improvements #135

Open
wants to merge 3 commits into
base: session-api-temp
Choose a base branch
from
Open
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 @@ -57,7 +57,10 @@ public enum ErrorMessage {
"The provided userId is invalid."),
ERROR_CODE_SERVER_ERROR(USER_MANAGEMENT_PREFIX.getPrefix() + "15001",
"Unable to retrieve User.",
"Server Encountered an error while retrieving the user.");
"Server Encountered an error while retrieving the user."),
INVALID_TENANT_DOMAIN(USER_MANAGEMENT_PREFIX.getPrefix() + "10002",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this is a server error. Shall we start the error code with 150xx instead of 100xx? We use 100xx for client errors.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

INVALID_TENANT_DOMAIN is a client error IMO... as the client could change the context tenantDomain to a non-existing one.

"Invalid tenant domain.",
"Server Encountered an error while retrieving tenantId for tenantDomain: %s.");

private final String code;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.wso2.carbon.identity.application.authentication.framework.exception.UserSessionException;
import org.wso2.carbon.identity.application.authentication.framework.store.UserSessionStore;
import org.wso2.carbon.identity.application.common.model.User;
import org.wso2.carbon.identity.base.IdentityRuntimeException;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.api.UserStoreManager;
Expand All @@ -37,6 +38,7 @@

import static org.wso2.carbon.identity.api.user.common.Constants.CORRELATION_ID_MDC;
import static org.wso2.carbon.identity.api.user.common.Constants.ErrorMessage.ERROR_CODE_INVALID_USERNAME;
import static org.wso2.carbon.identity.api.user.common.Constants.ErrorMessage.INVALID_TENANT_DOMAIN;
import static org.wso2.carbon.identity.api.user.common.Constants.ErrorMessage.ERROR_CODE_SERVER_ERROR;

/**
Expand Down Expand Up @@ -141,4 +143,27 @@ private static boolean validateUserIdInUserstore(RealmService realmService, Stri
}
return ((UniqueIDUserStoreManager) userStoreManager).isExistingUserWithID(userId);
}

/**
* Validate whether the given filter is not empty and tenantDomain is valid.
*
* @param filter Filter to be applied for session termination.
* @param tenantDomain Tenant domain of the requester.
*/
public static void validateFilter(String filter, String tenantDomain) {

if (StringUtils.isEmpty(filter)) {
throw new WebApplicationException("Filter is empty.");
}

try {
int tenantId = IdentityTenantUtil.getTenantId(tenantDomain);
} catch (IdentityRuntimeException e) {
throw new APIError(Response.Status.BAD_REQUEST, new ErrorResponse.Builder()
.withCode(INVALID_TENANT_DOMAIN.getCode())
.withMessage(INVALID_TENANT_DOMAIN.getMessage())
.withDescription(String.format(INVALID_TENANT_DOMAIN.getDescription(), tenantDomain))
.build(log, e, "Error occurred while retrieving tenantId for tenantDomain: " + tenantDomain));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.wso2.carbon.identity.rest.api.user.session.v1.dto.SearchResponseDTO;

import javax.validation.Valid;

import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
Expand Down Expand Up @@ -58,4 +60,22 @@ public Response getSessions(@ApiParam(value = "Condition to filter the retrieval
return delegate.getSessions(filter, limit, since, until);
}

@Valid
@DELETE
@ApiOperation(value = "Terminates the sessions selected based on a filtering criteria",
notes = "Terminates a number of sessions selected based on a provided filtering criteria.\n<b>Permission required:</b>\n * /permission/admin/login\n",
response = void.class)
@ApiResponses(value = {
@ApiResponse(code = 204, message = "No Content"),
@ApiResponse(code = 400, message = "Invalid input request"),
@ApiResponse(code = 401, message = "Unauthorized"),
@ApiResponse(code = 403, message = "Resource Forbidden"),
@ApiResponse(code = 500, message = "Internal Server Error")})
public Response terminateFilteredSessions(@ApiParam(value = "Condition to filter the retrieval of records.\nThe filter parameter must contain at least one valid expression (for multiple expressions they must be combined using the 'and' logical operator).\nEach expression must contain an attribute name followed by an attribute operator and a value (attribute names, operators and values used in filters are case insensitive).\n\nThe operators supported in the expression are listed next\n| Operator | Description | Behavior |\n|----------|-------------|----------|\n| eq | equal | The attribute and operator values must be identical for a match. |\n| sw | starts with | The entire operator value must be a substring of the attribute value, starting at the beginning of the attribute value. |\n| ew | ends with | The entire operator value must be a substring of the attribute value, matching at the end of the attribute value. |\n| co | contains | The entire operator value must be a substring of the attribute value for a match. |\n| le | less than or equal to | If the attribute value is less than or equal to the operator value, there is a match. |\n| ge | greater than or equal to | If the attribute value is greater than or equal to the operator value, there is a match. |\n\nThe attributes supported in the expression are listed next\n| Name | Operators | Description |\n|------|-----------|-------------|\n| loginId | eq, sw, ew, co | Filter results by the login identifier of the user who owns the session. |\n| sessionId | eq, sw, ew, co | Filter results by the ID of the session. |\n| appName | eq, sw, ew, co | Filter results by the name of the application related to the session. |\n| ipAddress | eq | Filter results by the IP address of the session. |\n| userAgent | eq, sw, ew, co | Filter results by the user agent of the session. |\n| loginTime | le, ge | Filter results by the login time of the session. |\n| lastAccessTime | le, ge | Filter results by the last access time of the session. |\n\n_Example, filter=loginId eq john and userAgent co Chrome_\n") @QueryParam("filter") String filter,
@ApiParam(value = "Maximum number of records to return.\n_Default value: 20_\n") @QueryParam("limit") Integer limit,
@ApiParam(value = "Unix timestamp data value that points to the start of the range of data to be returned.\n_Note: As results are ordered by more recent first this will provide previous page of results._\n") @QueryParam("since") Long since,
@ApiParam(value = "Unix timestamp data value that points to the end of the range of data to be returned.\n_Note: As results are ordered by more recent first this will provide next page of results._\n") @QueryParam("until") Long until) {

return delegate.terminateFilteredSessions(filter, limit, since, until);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,14 @@ public abstract class SessionsApiService {

public abstract Response getSessions(String filter, Integer limit, Long since, Long until);

/**
* Terminates sessions selected based on filter.
*
* @param filter the filter based on which the sessions to be terminated are selected (Mandatory)
* @param limit maximum number of sessions to be selected (Optional)
* @param since timestamp data value that points to the start of the range of data to be returned (Optional)
* @param until timestamp data value that points to the end of the range of data to be returned (Optional)
* @return Response
*/
public abstract Response terminateFilteredSessions(String filter, Integer limit, Long since, Long until);
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,30 @@ public SearchResponseDTO getSessions(String tenantDomain, String filter, Integer
}
}

/**
* Terminate active sessions based on a filter criteria.
*
* @param filter The filter based on which the sessions to be terminated are selected (Mandatory).
* @param limit Maximum number of sessions to be selected (Optional).
* @param since Timestamp data value that points to the start of the range of data to be returned (Optional).
* @param until Timestamp data value that points to the end of the range of data to be returned (Optional).
*/
public void terminateFilteredSessions(String tenantDomain, String filter, Integer limit, Long since, Long until) {

try {
List<ExpressionNode> filterNodes = getExpressionNodes(filter, since, until);
validateSearchFilter(filterNodes);

limit = limit == null || limit <= 0 ? SESSIONS_SEARCH_DEFAULT_LIMIT : limit;
String sortOrder = since != null ? SessionMgtConstants.ASC : SessionMgtConstants.DESC;

SessionManagementServiceHolder.getUserSessionManagementService()
.terminateFilteredSessions(tenantDomain, filterNodes, limit + 1, sortOrder);
} catch (SessionManagementException e) {
throw handleSessionManagementException(e);
}
}

/**
* Terminate the session of the given session id.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.springframework.beans.factory.annotation.Autowired;
import org.wso2.carbon.identity.api.user.common.ContextLoader;
import org.wso2.carbon.identity.api.user.common.Util;
import org.wso2.carbon.identity.rest.api.user.session.v1.SessionsApiService;
import org.wso2.carbon.identity.rest.api.user.session.v1.core.SessionManagementService;
import org.wso2.carbon.identity.rest.api.user.session.v1.dto.SearchResponseDTO;
Expand All @@ -40,4 +41,12 @@ public Response getSessions(String filter, Integer limit, Long since, Long until

return Response.ok().entity(responseDTO).build();
}

@Override
public Response terminateFilteredSessions(String filter, Integer limit, Long since, Long until) {

Util.validateFilter(filter, ContextLoader.getTenantDomainFromContext());
sessionManagementService.terminateFilteredSessions(ContextLoader.getTenantDomainFromContext(), filter, limit, since, until);
return Response.noContent().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,35 @@ paths:
500:
$ref: '#/responses/ServerError'

delete:
tags:
- sysadmin
description: Terminates a number of sessions selected based on a provided filtering criteria. <br>
<b>Permission required:</b> <br>
* /permission/admin/manage/identity/authentication/session/delete <br>
<b>Scope required:</b> <br>
* internal_session_delete
summary: Terminates the sessions selected based on a filtering criteria
operationId: terminateFilteredSessions
parameters:
- $ref: '#/parameters/filterQueryParam'
- $ref: '#/parameters/limitQueryParam'
- $ref: '#/parameters/sinceQueryParam'
- $ref: '#/parameters/untilQueryParam'
produces:
- application/json
responses:
204:
$ref: '#/responses/NoContent'
400:
$ref: '#/responses/InvalidInput'
401:
$ref: '#/responses/Unauthorized'
403:
$ref: '#/responses/Forbidden'
500:
$ref: '#/responses/ServerError'

#-----------------------------------------------------
# Descriptions of common responses
#-----------------------------------------------------
Expand Down