Commit 90723e9a authored by Maxym Borodenko's avatar Maxym Borodenko Committed by Matija Obreza
Browse files

OAuth token repository

parent 38a55028
......@@ -26,6 +26,7 @@ import org.genesys.blocks.security.NotUniqueUserException;
import org.genesys.filerepository.FileRepositoryException;
import org.genesys.filerepository.NoSuchRepositoryFileException;
import org.genesys.filerepository.NoSuchRepositoryFolderException;
import org.gringlobal.service.JPATokenStore;
import org.hibernate.hql.internal.ast.InvalidPathException;
import org.hibernate.hql.internal.ast.QuerySyntaxException;
import org.slf4j.Logger;
......@@ -169,7 +170,7 @@ public class ApiExceptionHandler {
* @return the api error
*/
@ResponseStatus(code = HttpStatus.NOT_FOUND)
@ExceptionHandler(value = { EntityNotFoundException.class, NotFoundElement.class, NoSuchRepositoryFileException.class, NoSuchRepositoryFolderException.class, NoUserFoundException.class })
@ExceptionHandler(value = { EntityNotFoundException.class, NotFoundElement.class, NoSuchRepositoryFileException.class, NoSuchRepositoryFolderException.class, NoUserFoundException.class, JPATokenStore.NoSuchTokenException.class })
@ResponseBody
public ApiError<Exception> handleNotFound(final Exception e, final HttpServletRequest request) {
LOG.warn("Element not found {} {}", request.getMethod(), request.getRequestURL());
......
/*
* Copyright 2021 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.api.v1.impl;
import java.util.List;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.gringlobal.api.v1.ApiBaseController;
import org.gringlobal.api.v1.FilteredPage;
import org.gringlobal.api.v1.Pagination;
import org.gringlobal.model.jwt.AccessToken;
import org.gringlobal.model.jwt.RefreshToken;
import org.gringlobal.service.JPATokenStore;
import org.gringlobal.service.filter.OAuthAccessTokenFilter;
import org.gringlobal.service.filter.OAuthRefreshTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Allow administrators to manage OAuth tokens.
*
* @author Maxym Borodenko
*/
@RestController("oauthTokenManagementApi1")
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping(OAuthTokenManagementController.CONTROLLER_PATH)
@Tag(name = "oauthTokenManagement")
public class OAuthTokenManagementController extends ApiBaseController {
public final static String CONTROLLER_PATH = ApiBaseController.APIv1_ADMIN_BASE + "/oauth-token";
@Autowired
private JPATokenStore tokenStore;
/**
* List access tokens.
*
* @param page the page
* @param filter the partner filter
* @return the page
*/
@PostMapping(value = "/list-access-tokens")
public FilteredPage<AccessToken, OAuthAccessTokenFilter> listAccessTokens(final Pagination page, @RequestBody(required = false) OAuthAccessTokenFilter filter) {
return new FilteredPage<>(filter, tokenStore.listAccessTokens(filter, page.toPageRequest(100, Sort.Direction.ASC, "client.clientId")));
}
/**
* List refresh tokens.
*
* @param page the page
* @param filter the partner filter
* @return the page
*/
@PostMapping(value = "/list-refresh-tokens")
public FilteredPage<RefreshToken, OAuthRefreshTokenFilter> listRefreshTokens(final Pagination page, @RequestBody(required = false) OAuthRefreshTokenFilter filter) {
return new FilteredPage<>(filter, tokenStore.listRefreshTokens(filter, page.toPageRequest(100, Sort.Direction.ASC, "client.clientId")));
}
/**
* Revoke all access tokens by the filter.
*
* @param filter the filter
* @return list of revoked token
*/
@PostMapping(value = "/revoke-access-tokens")
public List<AccessToken> revokeAccessTokens(@RequestBody final OAuthAccessTokenFilter filter) {
return tokenStore.revokeAllAccessTokens(filter);
}
/**
* Revoke all refresh tokens by the filter.
*
* @param filter the filter
* @return list of revoked token
*/
@PostMapping(value = "/revoke-refresh-tokens")
public List<RefreshToken> revokeRefreshTokens(@RequestBody final OAuthRefreshTokenFilter filter) {
return tokenStore.revokeAllRefreshTokens(filter);
}
/**
* Revoke access token.
*
* @param id the token id
* @return revoked token
*/
@DeleteMapping(value = "/access-token/{id}")
public AccessToken revokeAccessToken(@PathVariable("id") final long id) {
return tokenStore.revokeAccessToken(tokenStore.getAccessTokenById(id));
}
/**
* Revoke refresh token.
*
* @param id the token id
* @return revoked token
*/
@DeleteMapping(value = "/refresh-token/{id}")
public RefreshToken revokeRefreshToken(@PathVariable("id") final long id) {
return tokenStore.revokeRefreshToken(tokenStore.getRefreshTokenById(id));
}
}
......@@ -26,6 +26,7 @@ import org.genesys.blocks.oauth.service.OAuthServiceImpl;
import org.genesys.blocks.security.component.OAuthClientOriginCheckFilter;
import org.gringlobal.custom.security.CustomUserAuthenticationConverter;
import org.gringlobal.custom.security.WebUserTokenGranter;
import org.gringlobal.service.JPATokenStoreImpl;
import org.gringlobal.service.WebUserService;
import org.gringlobal.spring.AccessTokenInCookieFilter;
import org.gringlobal.spring.CachedInMemoryAuthorizationCodeServices;
......@@ -64,7 +65,6 @@ import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
......@@ -105,7 +105,7 @@ public class OAuth2ServerConfig {
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
return new JPATokenStoreImpl(accessTokenConverter());
}
/**
......
/*
* Copyright 2021 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.model.jwt;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* OAuth AccessToken.
*/
@Entity
@Table(name = "oauth_access_token")
public class AccessToken extends Token {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 6499194247567277729L;
/** The refresh token JTI. */
@Column(name = "refresh_token_jti")
private String refreshTokenJti;
/**
* Sets the refresh token.
*
* @param refreshToken the new refresh token
*/
public void setRefreshTokenJti(final String refreshToken) {
this.refreshTokenJti = refreshToken;
}
/**
* Gets the refresh token.
*
* @return the refresh token
*/
public String getRefreshTokenJti() {
return refreshTokenJti;
}
}
/*
* Copyright 2021 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.model.jwt;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* OAuth RefreshToken.
*/
@Entity
@Table(name = "oauth_refresh_token")
public class RefreshToken extends Token {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -9217916667924105240L;
}
/*
* Copyright 2021 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.model.jwt;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import org.genesys.blocks.model.BasicModel;
import org.genesys.blocks.oauth.model.OAuthClient;
@MappedSuperclass
public abstract class Token extends BasicModel {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -9113949717132094415L;
/** The token JTI. */
@Column(name = "token_jti", length = 28, unique = true, updatable = false, nullable = false)
private String tokenJti;
/** The username. */
@Column(length = 100)
private String username;
/** The OAuth client. */
@ManyToOne(cascade = {}, optional = false)
@JoinColumn(name = "client_id", updatable = false)
@JsonIdentityReference(alwaysAsId = true)
private OAuthClient client;
/** The expiration. */
@Temporal(TemporalType.TIMESTAMP)
private Date expiration;
/** The IP address of a client. */
@Column(name = "client_ip", length = 15)
private String clientIp;
/**
* Gets the IP.
*
* @return the IP
*/
public String getClientIp() {
return clientIp;
}
/**
* Sets the IP.
*
* @param clientIp the new IP
*/
public void setClientIp(final String clientIp) {
this.clientIp = clientIp;
}
/**
* Gets the token jti.
*
* @return the token jti
*/
public String getTokenJti() {
return tokenJti;
}
/**
* Sets the token jti.
*
* @param tokenJti the new token jti
*/
public void setTokenJti(String tokenJti) {
this.tokenJti = tokenJti;
}
/**
* Get the identifier of the user associated with this refresh token.
*
* @return Usually the UUID of the User
*/
public String getUsername() {
return username;
}
/**
* Sets the username.
*
* @param username the new username
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Get the oauth client for which this token is issued.
*
* @return OAuth client
*/
public OAuthClient getClient() {
return client;
}
/**
* Sets the oauth client.
*
* @param client the new client
*/
public void setClient(final OAuthClient client) {
this.client = client;
}
/**
* Gets the expiration.
*
* @return the expiration
*/
public Date getExpiration() {
return expiration;
}
/**
* Sets the expiration.
*
* @param expiration the new expiration
*/
public void setExpiration(Date expiration) {
this.expiration = expiration;
}
}
/*
* Copyright 2021 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.persistence.jwt;
import java.util.Date;
import java.util.List;
import org.genesys.blocks.oauth.model.OAuthClient;
import org.gringlobal.model.jwt.AccessToken;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
/**
* @author Maxym Borodenko
*/
public interface AccessTokenRepository extends JpaRepository<AccessToken, Long>, QuerydslPredicateExecutor<AccessToken> {
/**
* Delete by refresh token.
*
* @param refreshToken the refresh token
*/
@Modifying
@Query("delete from AccessToken at where at.refreshTokenJti = ?1")
void deleteByRefreshToken(String refreshToken);
/**
* Find by OAuth client.
*
* @param client the client
* @return the list
*/
List<AccessToken> findByClient(OAuthClient client);
/**
* Delete by token jti.
*
* @param jti the jti
*/
@Modifying
@Query("delete from AccessToken at where at.tokenJti = ?1")
void deleteByTokenJti(String jti);
/**
* Find by token jti.
*
* @param tokenJti the token jti
* @return found record
*/
AccessToken findByTokenJti(String tokenJti);
/**
* Find by OAuth client and username.
*
* @param client the client
* @param username the username
* @return the list
*/
List<AccessToken> findByClientAndUsername(OAuthClient client, String username);
/**
* Find by username.
*
* @param uuid the uuid
* @return the list
*/
List<AccessToken> findByUsername(String uuid);
/**
* Delete expired tokens.
*
* @param date the date
*/
@Query("delete from AccessToken at where at.expiration < ?1")
@Modifying
int deleteOlderThan(Date date);
}
/*
* Copyright 2021 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.persistence.jwt;
import java.util.Date;
import java.util.List;
import org.genesys.blocks.oauth.model.OAuthClient;
import org.gringlobal.model.jwt.RefreshToken;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
/**
* @author Maxym Borodenko
*/
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long>, QuerydslPredicateExecutor<RefreshToken> {
/**
* Find by Oauth client.
*
* @param client the client
* @return the list
*/
List<RefreshToken> findByClient(OAuthClient client);
/**
* Delete by token jti.
*
* @param jti the jti
*/
@Modifying
@Query("delete from RefreshToken rt where rt.tokenJti = ?1")
void deleteByTokenJti(String jti);
/**
* Find by token jti.
*
* @param tokenJti the tokenJti
* @return the list
*/
RefreshToken findByTokenJti(String tokenJti);
/**
* Delete expired tokens.
*
* @param date the date
*/
@Query("delete from RefreshToken rt where rt.expiration < ?1")
@Modifying
int deleteOlderThan(Date date);
}
/*
* Copyright 2021 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.service;
import java.util.List;
import org.gringlobal.model.jwt.AccessToken;
import org.gringlobal.model.jwt.RefreshToken;
import org.gringlobal.service.filter.OAuthAccessTokenFilter;
import org.gringlobal.service.filter.OAuthRefreshTokenFilter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* @author Maxym Borodenko
*/
public interface JPATokenStore extends TokenStore {
Page<AccessToken> listAccessTokens(OAuthAccessTokenFilter filter, Pageable page);
Page<RefreshToken> listRefreshTokens(OAuthRefreshTokenFilter filter, Pageable page);
AccessToken getAccessTokenByJti(String tokenJti);