├── .bazelversion ├── .gitignore ├── BUILD.bazel ├── LICENSE ├── README.md ├── WORKSPACE ├── doc ├── abstract_protocol_flow.png ├── rfc-6749.txt ├── rfc-7521.txt └── rfc-7523.txt ├── oauth2-example-app ├── BUILD.bazel ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── clouway │ │ │ └── oauth2 │ │ │ ├── Session.java │ │ │ ├── app │ │ │ ├── AppServer.java │ │ │ ├── AuthBootstrap.java │ │ │ └── JwtClient.java │ │ │ └── exampleapp │ │ │ ├── ClientRegistry.java │ │ │ ├── ErrorResponseDTO.java │ │ │ ├── GenerateNewRefreshToken.java │ │ │ ├── Json.java │ │ │ ├── LoginEndpoint.java │ │ │ ├── OAuthAuthorizationServerModule.java │ │ │ ├── RegistrationRequestDTO.java │ │ │ ├── RegistrationResponseDTO.java │ │ │ ├── ResourceOwner.java │ │ │ ├── ResourceOwnerAuthentication.java │ │ │ ├── ResourceOwnerStore.java │ │ │ ├── SessionSecurity.java │ │ │ ├── TokenTimeToLive.java │ │ │ ├── UserDto.java │ │ │ ├── UserInfoEndPoint.java │ │ │ ├── UserLoader.java │ │ │ ├── UserLoaderImpl.java │ │ │ ├── UserRepository.java │ │ │ ├── security │ │ │ ├── LoginPageUrl.java │ │ │ ├── OAuthSecurityFilter.java │ │ │ ├── SecuredResources.java │ │ │ ├── SecurityModule.java │ │ │ └── UriPath.java │ │ │ └── storage │ │ │ ├── InMemoryClientAuthorizer.kt │ │ │ ├── InMemoryClientRepository.java │ │ │ ├── InMemoryModule.java │ │ │ ├── InMemoryResourceOwnerRepository.kt │ │ │ ├── InMemoryTokens.kt │ │ │ └── InMemoryUserRepository.kt │ └── resources │ │ └── com │ │ └── clouway │ │ └── oauth2 │ │ └── exampleapp │ │ └── login.html │ └── test │ └── java │ └── com │ └── clouway │ └── oauth2 │ ├── SessionEqualityTest.java │ └── exampleapp │ ├── ClientRegistryContractTest.java │ ├── ErrorResponseDTOEqualityTest.java │ ├── FakeHttpServletRequest.java │ ├── FakeHttpServletResponse.java │ ├── LoginEndpointTest.java │ ├── RegistrationResponseDTOEqualityTest.java │ ├── ReplyMatchers.java │ ├── SiteBricksRequestMockery.java │ ├── security │ ├── OAuthSecurityFilterTest.java │ └── SecuredResourcesTest.java │ └── storage │ ├── InMemoryClientRepositoryTest.java │ └── InMemoryResourceOwnerRepositoryTest.java ├── oauth2-server ├── BUILD.bazel ├── shade.jarjar └── src │ ├── main │ └── java │ │ └── com │ │ └── clouway │ │ └── oauth2 │ │ ├── AuthCodeAuthorization.kt │ │ ├── AuthorizedClientActivity.kt │ │ ├── AuthorizedIdentityActivity.kt │ │ ├── BUILD.bazel │ │ ├── BearerTokenResponse.java │ │ ├── ClientActivity.java │ │ ├── ClientAuthenticationCredentialsRequest.java │ │ ├── ClientAuthorizationActivity.kt │ │ ├── ClientController.java │ │ ├── ClientRequest.kt │ │ ├── CodeExchangeVerificationFlow.java │ │ ├── IdentityActivity.java │ │ ├── IdentityAuthorizationActivity.kt │ │ ├── IdentityController.kt │ │ ├── InstantaneousRequest.java │ │ ├── InstantaneousRequestController.java │ │ ├── IssueNewTokenActivity.kt │ │ ├── JwtController.kt │ │ ├── OAuth2ApiSupport.java │ │ ├── OAuth2ApiSupportFactory.java │ │ ├── OAuth2Config.java │ │ ├── OAuth2Servlet.java │ │ ├── OAuthError.java │ │ ├── PublicCertsController.java │ │ ├── RefreshTokenActivity.kt │ │ ├── ResourceOwnerIdentityFinder.java │ │ ├── ResponseWriter.java │ │ ├── RevokeTokenController.java │ │ ├── TokenInfoController.kt │ │ ├── UserInfoController.kt │ │ ├── authorization │ │ ├── Authorization.kt │ │ ├── AuthorizationRequest.kt │ │ ├── AuthorizationResponse.java │ │ ├── BUILD.bazel │ │ └── ClientAuthorizer.kt │ │ ├── client │ │ ├── BUILD.bazel │ │ ├── Client.java │ │ ├── ClientCredentials.java │ │ ├── ClientFinder.kt │ │ ├── ClientRegistrationRequest.java │ │ ├── JwtKeyStore.java │ │ ├── RegistrationRequest.java │ │ └── RegistrationResponse.java │ │ ├── codechallenge │ │ ├── AuthorizationCodeVerifier.kt │ │ ├── BUILD.bazel │ │ ├── CodeChallenge.java │ │ └── CodeVerifier.java │ │ ├── common │ │ ├── BUILD.bazel │ │ ├── DateTime.java │ │ └── Duration.java │ │ ├── jws │ │ ├── BUILD.bazel │ │ ├── Pem.java │ │ ├── RsaJwsSignature.java │ │ ├── Signature.java │ │ └── SignatureFactory.java │ │ ├── jwt │ │ ├── BUILD.bazel │ │ └── Jwt.java │ │ ├── keystore │ │ ├── BUILD.bazel │ │ ├── IdentityKeyPair.java │ │ └── KeyStore.java │ │ ├── token │ │ ├── BUILD.bazel │ │ ├── BearerToken.java │ │ ├── FindIdentityRequest.java │ │ ├── GrantType.java │ │ ├── IdTokenFactory.java │ │ ├── Identity.kt │ │ ├── IdentityFinder.kt │ │ ├── JjwtIdTokenFactory.kt │ │ ├── ServiceAccount.kt │ │ ├── TokenErrorResponse.java │ │ ├── TokenGenerator.java │ │ ├── TokenRequest.kt │ │ ├── TokenResponse.java │ │ ├── Tokens.java │ │ ├── UrlSafeTokenGenerator.java │ │ └── User.java │ │ └── util │ │ ├── BUILD.bazel │ │ └── Params.java │ └── test │ └── java │ └── com │ └── clouway │ └── oauth2 │ ├── AuthCodeAuthorizationTest.java │ ├── AuthorizationResponseTest.java │ ├── AuthorizeClientsTest.java │ ├── BUILD.bazel │ ├── ByteRequest.java │ ├── ClientEqualityTest.java │ ├── CodeExchangeVerificationFlowTest.java │ ├── DecodeClientCredentialsTest.java │ ├── GetTokenInfoTest.java │ ├── HandleJwtTokenRequestsTest.kt │ ├── IdentityAuthorizationActivityTest.java │ ├── IdentityControllerTest.kt │ ├── IssueNewTokenForClientTest.kt │ ├── RefreshTokenForClientTest.java │ ├── ResourceOwnerClientAuthorizationTest.java │ ├── RetrievePublicCertsTest.java │ ├── RetrieveUserInfoWithAccessTokenTest.java │ ├── RevokeTokensTest.java │ ├── SerializeBearerTokensTest.java │ ├── authorization │ ├── AuthorizationBuilder.java │ └── BUILD.bazel │ ├── client │ ├── BUILD.bazel │ └── ClientBuilder.java │ ├── codechallenge │ ├── AuthorizationCodeVerifierTest.java │ └── BUILD.bazel │ ├── common │ ├── BUILD.bazel │ ├── CalendarUtil.java │ ├── CommonMatchers.java │ ├── DateTimeAddSecondsTest.java │ ├── DateTimeEqualityTest.java │ ├── DurationEqualityTest.java │ └── DurationTest.java │ ├── jws │ ├── BUILD.bazel │ ├── PemTest.java │ ├── ReadPemFilesTest.java │ ├── VerifySignaturesWithRsaTest.java │ └── testdata │ │ └── secret.pem │ ├── token │ ├── BUILD.bazel │ ├── BearerTokenBuilder.java │ ├── IdentityBuilder.java │ ├── JjwtIdTokenFactoryTest.java │ ├── TokenEqualityTest.java │ ├── TokenIsExpiredAtTest.java │ ├── UrlSafeTokenGeneratorTest.java │ └── VerifyingJjwtTokensTest.kt │ └── util │ ├── ArgumentCaptor.java │ ├── BUILD.bazel │ ├── ParamsTest.java │ └── PemKeyGenerator.java └── tools └── jvm ├── BUILD.bazel └── variables.bzl /.bazelversion: -------------------------------------------------------------------------------- 1 | 6.3.2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .shelf 2 | .idea/ 3 | target 4 | */target/* 5 | *.iml 6 | *.ipr 7 | *.iws 8 | *.log 9 | *~ 10 | *.6 11 | *.8 12 | _obj 13 | a.out 14 | runsit 15 | test/daemon/testdaemon 16 | _cgo_export.h 17 | /pkg 18 | /machine* 19 | /bin 20 | # Ignore Vim swap files. 21 | .*.swp 22 | # Ignore files generated by IDEs. 23 | /.classpath 24 | /.factorypath 25 | /.idea/ 26 | /.ijwb/ 27 | /.project 28 | /.settings 29 | /.vscode/ 30 | /bazel.iml 31 | # Ignore all bazel-* symlinks. There is no full list since this can change 32 | # based on the name of the directory bazel is cloned into. 33 | /bazel-* 34 | # Ignore outputs generated during Bazel bootstrapping. 35 | /output/ -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:core.bzl", "define_kt_toolchain", "kt_kotlinc_options") 2 | 3 | kt_kotlinc_options( 4 | name = "kt_kotlinc_options", 5 | x_skip_prerelease_check = True, 6 | ) 7 | 8 | define_kt_toolchain( 9 | name = "kotlin_toolchain", 10 | api_version = "1.6", # "1.1", "1.2", "1.3", "1.4", "1.5" "1.6", or "1.7" 11 | jvm_target = "11", # "1.6", "1.8", "9", "10", "11", "12", "13", "15", "16", or "17" 12 | kotlinc_options = "//:kt_kotlinc_options", 13 | language_version = "1.6", # "1.1", "1.2", "1.3", "1.4", "1.5" "1.6", or "1.7" 14 | ) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 clouWay ood. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /doc/abstract_protocol_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clouway/oauth2-server/0aad3b0c011cbb477ef6481265d42a2703ad6e0d/doc/abstract_protocol_flow.png -------------------------------------------------------------------------------- /oauth2-example-app/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "oauth2-example-app", 7 | srcs = glob([ 8 | "src/main/java/**/*.java", 9 | "src/main/java/**/*.kt", 10 | ]), 11 | deps = [ 12 | "//oauth2-server:jarjar", 13 | "@maven//:ch_qos_logback_logback_classic", 14 | "@maven//:com_clouway_fserve_fserve", 15 | "@maven//:com_clouway_security_jwt_java_client_okhttp", 16 | "@maven//:com_fasterxml_jackson_core_jackson_annotations", 17 | "@maven//:com_fasterxml_jackson_core_jackson_core", 18 | "@maven//:com_fasterxml_jackson_core_jackson_databind", 19 | "@maven//:com_google_code_gson_gson", 20 | "@maven//:com_google_guava_guava", 21 | "@maven//:com_google_inject_extensions_guice_servlet", 22 | "@maven//:com_google_inject_guice", 23 | "@maven//:com_google_sitebricks_sitebricks", 24 | "@maven//:com_google_sitebricks_sitebricks_annotations", 25 | "@maven//:com_google_sitebricks_sitebricks_client", 26 | "@maven//:com_google_sitebricks_sitebricks_converter", 27 | "@maven//:com_squareup_okhttp3_okhttp", 28 | "@maven//:io_jsonwebtoken_jjwt_api", 29 | "@maven//:io_jsonwebtoken_jjwt_impl", 30 | "@maven//:io_jsonwebtoken_jjwt_jackson", 31 | "@maven//:javax_annotation_javax_annotation_api", 32 | "@maven//:javax_json_javax_json_api", 33 | "@maven//:net_hamnaberg_json_json_javax", 34 | "@maven//:org_eclipse_jetty_jetty_server", 35 | "@maven//:org_eclipse_jetty_jetty_servlet", 36 | ], 37 | ) 38 | 39 | 40 | kt_jvm_binary( 41 | name = "auth-bootstrap", 42 | srcs = [ 43 | "src/main/java/com/clouway/oauth2/app/AuthBootstrap.java", 44 | ], 45 | main_class = "com.clouway.oauth2.app.AuthBootstrap", 46 | deps = [ 47 | ":oauth2-example-app", 48 | 49 | ], 50 | ) 51 | 52 | kt_jvm_binary( 53 | name = "jwt-client", 54 | srcs = [ 55 | "src/main/java/com/clouway/oauth2/app/JwtClient.java", 56 | ], 57 | main_class = "com.clouway.oauth2.app.JwtClient", 58 | deps = [ 59 | ":oauth2-example-app", 60 | 61 | ], 62 | ) 63 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/Session.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | /** 4 | * @author Ivan Stefanov 5 | */ 6 | public class Session { 7 | public final String value; 8 | 9 | public Session(String value) { 10 | this.value = value; 11 | } 12 | 13 | @Override 14 | public boolean equals(Object o) { 15 | if (this == o) return true; 16 | if (o == null || getClass() != o.getClass()) return false; 17 | 18 | Session session = (Session) o; 19 | 20 | if (value != null ? !value.equals(session.value) : session.value != null) return false; 21 | 22 | return true; 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return value != null ? value.hashCode() : 0; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "Session{" + 33 | "value='" + value + '\'' + 34 | '}'; 35 | } 36 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/app/AppServer.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.app; 2 | 3 | import com.clouway.oauth2.exampleapp.OAuthAuthorizationServerModule; 4 | import com.clouway.oauth2.exampleapp.storage.InMemoryModule; 5 | import com.google.inject.Guice; 6 | import com.google.inject.Injector; 7 | import com.google.inject.servlet.GuiceFilter; 8 | import com.google.inject.servlet.GuiceServletContextListener; 9 | import org.eclipse.jetty.server.Server; 10 | import org.eclipse.jetty.servlet.DefaultServlet; 11 | import org.eclipse.jetty.servlet.ServletContextHandler; 12 | 13 | import javax.servlet.DispatcherType; 14 | import java.util.EnumSet; 15 | 16 | /** 17 | * @author Ivan Stefanov 18 | */ 19 | public class AppServer { 20 | private final Server server; 21 | 22 | public AppServer(Integer port) { 23 | server = new Server(port); 24 | } 25 | 26 | public void start() throws Exception { 27 | ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); 28 | context.setContextPath("/"); 29 | 30 | context.addServlet(DefaultServlet.class, "/"); 31 | context.addFilter(GuiceFilter.class, "/*", EnumSet.allOf(DispatcherType.class)); 32 | 33 | context.addEventListener(new GuiceServletContextListener() { 34 | @Override 35 | protected Injector getInjector() { 36 | return Guice.createInjector(new OAuthAuthorizationServerModule("/r/oauth/login", 60 * 60L), new InMemoryModule()); 37 | } 38 | }); 39 | 40 | server.setHandler(context); 41 | server.start(); 42 | 43 | Runtime.getRuntime().addShutdownHook(new Thread() { 44 | @Override 45 | public void run() { 46 | if (server != null) { 47 | try { 48 | server.stop(); 49 | } catch (Exception e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | } 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/app/AuthBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.app; 2 | 3 | /** 4 | * @author Ivan Stefanov 5 | */ 6 | public class AuthBootstrap { 7 | private static final Integer PORT = 9002; 8 | 9 | public static void main(String[] args) { 10 | 11 | AppServer server = new AppServer(PORT); 12 | try { 13 | server.start(); 14 | } catch (Exception e) { 15 | e.printStackTrace(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/app/JwtClient.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.app; 2 | 3 | import com.clouway.oauth2.client.BearerAuthenticationInterceptor; 4 | import com.clouway.oauth2.client.JwtConfig; 5 | import com.clouway.oauth2.client.TokenSource; 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.Request; 8 | import okhttp3.Response; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * @author Vasil Mitov 14 | */ 15 | public class JwtClient { 16 | private static String key = "-----BEGIN PRIVATE KEY-----\n" + 17 | "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCH/eazwg0BwuFx\n" + 18 | "PmXOoauqD54ZPN+3XRF8FxrYo0XvQ8TiJAEJBJo/qjNahn4YYl/6RbP8YLHCe3nd\n" + 19 | "40tf42fwvLDFBSyFjKIOOiEEilhxjVX1jPsE4fHO+gSthmyzGgjR4bPCPLCeocQj\n" + 20 | "UQTguDsl2NARDWHomeC0eJgkS9cPfBdRGyIsgQWsnON+SbMd7cN9iXbIhU/TguLE\n" + 21 | "aW6WAg/rGX9iMiSsl5XcW6wOGU24uRPTNIySzncLY+jEQeFmEw1g0oa7+ZKuxDce\n" + 22 | "jJTzo4V0BcEKNiiZvH4Y7t/qsXiDeY13NemZVMQ/H9BIyfBpPdiTphnZEwf5XlUJ\n" + 23 | "pKekD/KBAgMBAAECggEAZeFXltAIAovHbZl7mAQSoUM2BF5QlASLdtWwbSBU4l15\n" + 24 | "AJpMlD74eD3AX09m5Em+8baKksa2Jadvs0X3UA0D75zNKa0on5yuQ85UshwbCmcC\n" + 25 | "QQWvgQbsq00vd/i/MqaMeQCINTpWb2Ftma+24cvjtATsS/okoae2aj32bSrMIXKK\n" + 26 | "V9UC2IwN5rzoJPEFQ6fPaWcTnFMOINY1UFDerVNhn7aT18ubB25N3TcQFA+IkLjZ\n" + 27 | "+RXAVsDqmmdiCA0IxF18SkC22TCLvWMW8pYAYWYi9MLOCwwjUwv1C3w38qvlzfRP\n" + 28 | "VDObsDgrmYA5xyJU1LIzvnaWYa+br6VmnBdCqBTTSQKBgQDzc+pkjol6qDVJb8ZD\n" + 29 | "oa2mNZJCu3dNYSuB9fYGutIT8/BuBZ+qkmb54C6Ny7M0HjpxQpSOToOpH95HlznJ\n" + 30 | "NLY80s76E9MdX6xFIlTpBRJnu3iPizCXRQV5C0mwleARVsk0FjE5T0XLQMp3l1bd\n" + 31 | "AGMGJhM9fM0W3mx2gmTjU+DRGwKBgQCPACjFI1aFIbFtpOQtFgHtCMdymhzLVabc\n" + 32 | "SyY7fnMoQiiPJMja9reFiSuJbF/BpsAxaOKEaVjivPM3arVaf3pyrqC3ZEwurfwx\n" + 33 | "u+mnXhUftT8/1esZZBGhFPA321zIaDucKx7yfOgg+gRdi6r4gpYD0pQo7sco86Ve\n" + 34 | "ARzhgUWgkwKBgGVsaj8gXsgZ4bFJfrjYV4bCFL/2Z7p1+/E1rhyZokGrxAOiFiWy\n" + 35 | "vnHlYp+yOGNDIKfkzA0JSrKf0zPSHcHkUvO+A3qN3csD+7oFlohJk6RhptVucHzk\n" + 36 | "xWXrPPTzS5kNpd8sS6+LhhEqWe8+vnJt4dNC84sPPkYDvf4VTsCiRiv3AoGAGjx1\n" + 37 | "PnYVUae03eD63CrFf6+0qBoOXmAAlTpUcWXpyuEYf+rHzySk1yMrkbMIfocRi/8q\n" + 38 | "UBDj9fWkye4SB+CLnq7bXcpRD99r/dP0MnjYd1DRoeyljasGcP9ec2ETzNES3rwq\n" + 39 | "mWLBVAuK8X7Gh4Gt9FWWSUxFzgWluXGK0vTcyXECgYA7GyyBORukyfOb5mCrIXm5\n" + 40 | "kYztpvfhglrUZ23vEzXLk+KPmDao0X3K6fv6OuvuI2oVAZ6TzTT1OlmF/elvP7JX\n" + 41 | "4vlOXSxLBduB1cInuZFylB99qRmGMCBWhpIobXyRQIZWnaQnsGDfFJiBrBgzN55U\n" + 42 | "UqgbFBNjeedWV+Hm6ftwxw==\n" + 43 | "-----END PRIVATE KEY-----"; 44 | 45 | // JWT/Service Account Authorization 46 | // OAuth2 Client Authorization -> web app -> API Gateway 47 | public static void main(String[] args) { 48 | JwtConfig config = new JwtConfig.Builder( 49 | "xxx@apps.clouway.com", 50 | "http://localhost:9002/oauth2/token", 51 | key.getBytes()) 52 | .subject("myapp ") 53 | .build(); 54 | TokenSource tokenSource = config.tokenSource(); 55 | OkHttpClient client = new OkHttpClient.Builder() 56 | .addInterceptor(new BearerAuthenticationInterceptor(tokenSource)) 57 | .build(); 58 | try { 59 | Response response = client.newCall(new Request.Builder().get().url("http://localhost:9002/testapi").build()).execute(); 60 | System.out.println(response.body().string()); 61 | } catch (IOException e) { 62 | e.printStackTrace(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/ClientRegistry.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.clouway.oauth2.client.Client; 4 | import com.clouway.oauth2.client.ClientRegistrationRequest; 5 | 6 | /** 7 | * @author Ianislav Nachev 8 | */ 9 | public interface ClientRegistry { 10 | 11 | Client register(Client client); 12 | 13 | Client register(ClientRegistrationRequest request); 14 | } 15 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/ErrorResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * @author Ivan Stefanov 7 | */ 8 | class ErrorResponseDTO { 9 | @SerializedName("error") 10 | private final String code; 11 | 12 | @SerializedName("error_description") 13 | private final String description; 14 | 15 | public ErrorResponseDTO(String code, String description) { 16 | this.code = code; 17 | this.description = description; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (o == null || getClass() != o.getClass()) return false; 24 | 25 | ErrorResponseDTO that = (ErrorResponseDTO) o; 26 | 27 | if (code != null ? !code.equals(that.code) : that.code != null) return false; 28 | if (description != null ? !description.equals(that.description) : that.description != null) return false; 29 | 30 | return true; 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | int result = code != null ? code.hashCode() : 0; 36 | result = 31 * result + (description != null ? description.hashCode() : 0); 37 | return result; 38 | } 39 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/GenerateNewRefreshToken.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.google.inject.BindingAnnotation; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * @author Mihail Lesikov (mlesikov@gmail.com) 12 | */ 13 | 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target({ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD}) 16 | @BindingAnnotation 17 | public @interface GenerateNewRefreshToken { 18 | } 19 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/Json.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.inject.Inject; 5 | import com.google.inject.TypeLiteral; 6 | import com.google.sitebricks.client.Transport; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.InputStreamReader; 11 | import java.io.OutputStream; 12 | 13 | /** 14 | * @author Miroslav Genov (mgenov@gmail.com) 15 | */ 16 | class Json implements Transport { 17 | private Gson gson; 18 | 19 | @Inject 20 | Json(Gson gson) { 21 | this.gson = gson; 22 | } 23 | 24 | public T in(InputStream inputStream, Class tClass) throws IOException { 25 | return gson.fromJson(new InputStreamReader(inputStream, "UTF-8"), tClass); 26 | } 27 | 28 | @Override 29 | public T in(InputStream inputStream, TypeLiteral typeLiteral) throws IOException { 30 | return gson.fromJson(new InputStreamReader(inputStream,"UTF-8"), typeLiteral.getType()); 31 | } 32 | 33 | public void out(OutputStream outputStream, Class tClass, T t) { 34 | String json = gson.toJson(t); 35 | try { 36 | outputStream.write(json.getBytes("UTF8")); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | 42 | public String contentType() { 43 | return "application/json"; 44 | } 45 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/LoginEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.clouway.oauth2.Session; 4 | import com.google.common.base.Optional; 5 | import com.google.inject.Inject; 6 | import com.google.sitebricks.Show; 7 | import com.google.sitebricks.http.Post; 8 | 9 | import java.io.UnsupportedEncodingException; 10 | import java.net.URLEncoder; 11 | 12 | import javax.servlet.http.Cookie; 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | 16 | /** 17 | * @author Ivan Stefanov 18 | */ 19 | @Show("login.html") 20 | public class LoginEndpoint { 21 | private String username; 22 | private String password; 23 | private String errormessage; 24 | 25 | private ResourceOwnerAuthentication authentication; 26 | 27 | @Inject 28 | public LoginEndpoint(ResourceOwnerAuthentication authentication) { 29 | this.authentication = authentication; 30 | } 31 | 32 | @Post 33 | public String login(HttpServletRequest request, HttpServletResponse response) { 34 | Optional session = authentication.auth(username, password, request.getRemoteAddr()); 35 | String continueUrl = request.getParameter("continue"); 36 | 37 | if (session.isPresent()) { 38 | // "SID" -this value should be configurable or in the 39 | // login should be responsible for that hardcoded string 40 | Cookie sid = new Cookie("SID", session.get().value); 41 | sid.setPath("/"); 42 | 43 | response.addCookie(sid); 44 | 45 | return continueUrl; 46 | } else { 47 | try { 48 | return request.getRequestURI() + 49 | "?continue="+URLEncoder.encode(continueUrl,"UTF-8")+ 50 | "&errormessage="+URLEncoder.encode("Invalid username or password","UTF-8"); 51 | } catch (UnsupportedEncodingException e) { 52 | return request.getRequestURI() + 53 | "?continue="+URLEncoder.encode(continueUrl)+ 54 | "&errormessage="+URLEncoder.encode("Invalid username or password"); 55 | } 56 | } 57 | } 58 | 59 | public void setUsername(String username) { 60 | this.username = username; 61 | } 62 | 63 | public void setPassword(String password) { 64 | this.password = password; 65 | } 66 | 67 | public String getErrormessage() { 68 | return errormessage; 69 | } 70 | 71 | public void setErrormessage(String errormessage) { 72 | this.errormessage = errormessage; 73 | } 74 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/RegistrationRequestDTO.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | /** 4 | * @author Ivan Stefanov 5 | */ 6 | class RegistrationRequestDTO { 7 | private String name; 8 | private String url; 9 | private String description; 10 | private String redirectURI; 11 | 12 | RegistrationRequestDTO() { 13 | } 14 | 15 | RegistrationRequestDTO(String name, String url, String description, String redirectURI) { 16 | this.name = name; 17 | this.url = url; 18 | this.description = description; 19 | this.redirectURI = redirectURI; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public String getUrl() { 27 | return url; 28 | } 29 | 30 | public String getDescription() { 31 | return description; 32 | } 33 | 34 | public String getRedirectURI() { 35 | return redirectURI; 36 | } 37 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/RegistrationResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * @author Ivan Stefanov 7 | */ 8 | class RegistrationResponseDTO { 9 | @SerializedName("client_id") 10 | private final String clientId; 11 | 12 | @SerializedName("client_secret") 13 | private final String clientSecret; 14 | 15 | RegistrationResponseDTO(String clientId, String clientSecret) { 16 | this.clientId = clientId; 17 | this.clientSecret = clientSecret; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (o == null || getClass() != o.getClass()) return false; 24 | 25 | RegistrationResponseDTO that = (RegistrationResponseDTO) o; 26 | 27 | if (clientId != null ? !clientId.equals(that.clientId) : that.clientId != null) return false; 28 | if (clientSecret != null ? !clientSecret.equals(that.clientSecret) : that.clientSecret != null) return false; 29 | 30 | return true; 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | int result = clientId != null ? clientId.hashCode() : 0; 36 | result = 31 * result + (clientSecret != null ? clientSecret.hashCode() : 0); 37 | return result; 38 | } 39 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/ResourceOwner.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | /** 4 | * @author Ivan Stefanov 5 | */ 6 | public final class ResourceOwner { 7 | public final String username; 8 | public final String password; 9 | 10 | public ResourceOwner(String username, String password) { 11 | this.username = username; 12 | this.password = password; 13 | } 14 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/ResourceOwnerAuthentication.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.clouway.oauth2.Session; 4 | import com.google.common.base.Optional; 5 | 6 | /** 7 | * @author Ivan Stefanov 8 | */ 9 | public interface ResourceOwnerAuthentication { 10 | Optional auth(String username, String password, String remoteAddress); 11 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/ResourceOwnerStore.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | /** 4 | * @author Ivan Stefanov 5 | */ 6 | public interface ResourceOwnerStore { 7 | void save(ResourceOwner resourceOwner); 8 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/SessionSecurity.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.clouway.oauth2.Session; 4 | 5 | /** 6 | * @author Ivan Stefanov 7 | */ 8 | public interface SessionSecurity { 9 | Boolean exists(Session session); 10 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/TokenTimeToLive.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.google.inject.BindingAnnotation; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * @author Mihail Lesikov (mlesikov@gmail.com) 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD}) 15 | @BindingAnnotation 16 | public @interface TokenTimeToLive { 17 | } 18 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/UserDto.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * @author Mihail Lesikov (mlesikov@gmail.com) 7 | */ 8 | public class UserDto { 9 | 10 | @SerializedName("id") 11 | public final String id; 12 | @SerializedName("email") 13 | public final String email; 14 | @SerializedName("fullName") 15 | public final String name; 16 | 17 | public UserDto(String id, String email, String name) { 18 | this.id = id; 19 | this.email = email; 20 | this.name = name; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) return true; 26 | if (!(o instanceof UserDto)) return false; 27 | 28 | UserDto userDto = (UserDto) o; 29 | 30 | if (email != null ? !email.equals(userDto.email) : userDto.email != null) return false; 31 | if (id != null ? !id.equals(userDto.id) : userDto.id != null) return false; 32 | if (name != null ? !name.equals(userDto.name) : userDto.name != null) return false; 33 | 34 | return true; 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | int result = id != null ? id.hashCode() : 0; 40 | result = 31 * result + (email != null ? email.hashCode() : 0); 41 | result = 31 * result + (name != null ? name.hashCode() : 0); 42 | return result; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/UserInfoEndPoint.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.clouway.oauth2.token.User; 4 | import com.google.common.base.Optional; 5 | import com.google.inject.Inject; 6 | import com.google.inject.name.Named; 7 | import com.google.sitebricks.headless.Reply; 8 | import com.google.sitebricks.headless.Service; 9 | import com.google.sitebricks.http.Get; 10 | import com.google.sitebricks.http.Post; 11 | 12 | import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; 13 | 14 | /** 15 | * @author Mihail Lesikov (mlesikov@gmail.com) 16 | */ 17 | @Service 18 | public class UserInfoEndPoint { 19 | 20 | 21 | private final UserLoader userLoader; 22 | 23 | @Inject 24 | public UserInfoEndPoint(UserLoader userLoader) { 25 | this.userLoader = userLoader; 26 | } 27 | 28 | @Post 29 | @Get 30 | public Reply getUserInfo(@Named("token") String token) { 31 | 32 | Optional userResult = userLoader.load(token); 33 | 34 | if (userResult.isPresent()) { 35 | User user = userResult.get(); 36 | return Reply.with(new UserDto(user.id, user.email, user.name)).as(Json.class).ok(); 37 | } 38 | 39 | return Reply.with(new ErrorResponseDTO("123", "User not found or expired token!")).status(SC_BAD_REQUEST).as(Json.class); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/UserLoader.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.clouway.oauth2.token.User; 4 | import com.google.common.base.Optional; 5 | 6 | /** 7 | * @author Mihail Lesikov (mlesikov@gmail.com) 8 | */ 9 | public interface UserLoader { 10 | Optional load(String token); 11 | } 12 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/UserLoaderImpl.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.clouway.oauth2.common.DateTime; 4 | import com.clouway.oauth2.token.BearerToken; 5 | import com.clouway.oauth2.token.Tokens; 6 | import com.clouway.oauth2.token.User; 7 | import com.google.common.base.Optional; 8 | 9 | /** 10 | * @author Mihail Lesikov (mlesikov@gmail.com) 11 | */ 12 | public class UserLoaderImpl implements UserLoader { 13 | 14 | private UserRepository repository; 15 | private Tokens tokens; 16 | 17 | public UserLoaderImpl(UserRepository repository, Tokens tokens) { 18 | this.repository = repository; 19 | this.tokens = tokens; 20 | } 21 | 22 | @Override 23 | public Optional load(String tokenValue) { 24 | 25 | Optional token = tokens.findTokenAvailableAt(tokenValue, new DateTime()); 26 | if (!token.isPresent()) { 27 | return Optional.absent(); 28 | } 29 | 30 | Optional user = repository.load(token.get().identityId); 31 | if (!token.isPresent()) { 32 | return Optional.absent(); 33 | } 34 | 35 | return user; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.clouway.oauth2.token.User; 4 | import com.google.common.base.Optional; 5 | 6 | /** 7 | * @author Mihail Lesikov (mlesikov@gmail.com) 8 | */ 9 | public interface UserRepository { 10 | Optional load(String userId); 11 | } 12 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/security/LoginPageUrl.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.security; 2 | 3 | /** 4 | * @author Mihail Lesikov (mlesikov@gmail.com) 5 | */ 6 | public @interface LoginPageUrl { 7 | } 8 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/security/OAuthSecurityFilter.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.security; 2 | 3 | import com.clouway.oauth2.Session; 4 | import com.clouway.oauth2.exampleapp.SessionSecurity; 5 | import com.google.common.base.Strings; 6 | import com.google.inject.Inject; 7 | import com.google.inject.Singleton; 8 | 9 | import javax.servlet.Filter; 10 | import javax.servlet.FilterChain; 11 | import javax.servlet.FilterConfig; 12 | import javax.servlet.ServletException; 13 | import javax.servlet.ServletRequest; 14 | import javax.servlet.ServletResponse; 15 | import javax.servlet.http.Cookie; 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpServletResponse; 18 | import java.io.IOException; 19 | import java.io.UnsupportedEncodingException; 20 | import java.net.URLEncoder; 21 | 22 | /** 23 | * @author Ivan Stefanov 24 | */ 25 | @Singleton 26 | public class OAuthSecurityFilter implements Filter { 27 | private final SessionSecurity sessionSecurity; 28 | private final SecuredResources securedResources; 29 | private String uriPath; 30 | private String loginPagePath; 31 | 32 | @Inject 33 | public OAuthSecurityFilter(SessionSecurity sessionSecurity, SecuredResources securedResources, @UriPath String uriPath, @LoginPageUrl String loginPagePath) { 34 | this.sessionSecurity = sessionSecurity; 35 | this.securedResources = securedResources; 36 | this.uriPath = uriPath; 37 | this.loginPagePath = loginPagePath; 38 | } 39 | 40 | @Override 41 | public void init(FilterConfig filterConfig) throws ServletException { 42 | 43 | } 44 | 45 | @Override 46 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { 47 | HttpServletRequest request = (HttpServletRequest) servletRequest; 48 | HttpServletResponse response = (HttpServletResponse) servletResponse; 49 | 50 | if (securedResources.contains(request.getRequestURI())) { 51 | //"SID" - this value should be configurable 52 | Session session = new Session(getCookieValue(request, "SID")); 53 | 54 | if (!sessionSecurity.exists(session)) { 55 | 56 | String path = uriPath + "/login"; 57 | 58 | if (!Strings.isNullOrEmpty(loginPagePath)) { 59 | path = loginPagePath; 60 | } 61 | 62 | String location = path + "?redirectUrl=" + getRedirectURI(request); 63 | 64 | response.sendRedirect(location); 65 | return; 66 | } 67 | } 68 | 69 | chain.doFilter(request, response); 70 | } 71 | 72 | @Override 73 | public void destroy() { 74 | 75 | } 76 | 77 | /** 78 | * Find and returns the value of the cookie with the provided name 79 | * 80 | * @param request http request from which cookie will be extracted 81 | * @param name cookie name 82 | * @return the value of the cookie if found null otherwise 83 | */ 84 | private String getCookieValue(HttpServletRequest request, String name) { 85 | Cookie[] cookies = request.getCookies(); 86 | 87 | if (cookies != null) { //Stupid servlet API! 88 | for (Cookie cookie : cookies) { 89 | if (name.equals(cookie.getName())) { 90 | return cookie.getValue(); 91 | } 92 | } 93 | } 94 | 95 | return ""; 96 | } 97 | 98 | private String getRedirectURI(HttpServletRequest request) throws UnsupportedEncodingException { 99 | String query = (request.getQueryString() == null) ? "" : "?" + request.getQueryString(); 100 | return URLEncoder.encode(request.getRequestURI() + query, "UTF-8"); 101 | } 102 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/security/SecuredResources.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.security; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | 5 | import java.util.Set; 6 | 7 | /** 8 | * @author Ivan Stefanov 9 | */ 10 | public class SecuredResources { 11 | private final Set resources; 12 | 13 | public SecuredResources(Set resources) { 14 | this.resources = ImmutableSet.copyOf(resources); 15 | } 16 | 17 | public Boolean contains(String resource) { 18 | return resources.contains(resource); 19 | } 20 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/security/SecurityModule.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.security; 2 | 3 | import com.google.common.collect.Sets; 4 | import com.google.inject.AbstractModule; 5 | import com.google.inject.Provides; 6 | import com.google.inject.Singleton; 7 | 8 | public class SecurityModule extends AbstractModule { 9 | 10 | private String url = ""; 11 | private String loginPageUrl = ""; 12 | 13 | public SecurityModule(String url) { 14 | this.url = url; 15 | } 16 | 17 | public SecurityModule(String url, String loginPageUrl) { 18 | this.url = url; 19 | this.loginPageUrl = loginPageUrl; 20 | } 21 | 22 | protected void configure() { 23 | 24 | } 25 | 26 | @Provides 27 | @Singleton 28 | public SecuredResources provideSecuredResources() { 29 | return new SecuredResources(Sets.newHashSet("/oauth2/auth")); 30 | } 31 | 32 | @Provides 33 | @UriPath 34 | public String uriPath() { 35 | return url; 36 | } 37 | 38 | @Provides 39 | @LoginPageUrl 40 | public String loginPageUrl() { 41 | return loginPageUrl; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/security/UriPath.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.security; 2 | 3 | import com.google.inject.BindingAnnotation; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * @author Mihail Lesikov (mlesikov@gmail.com) 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD}) 15 | @BindingAnnotation 16 | public @interface UriPath { 17 | } 18 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/storage/InMemoryClientAuthorizer.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.storage 2 | 3 | import com.clouway.oauth2.authorization.Authorization 4 | import com.clouway.oauth2.authorization.AuthorizationRequest 5 | import com.clouway.oauth2.authorization.ClientAuthorizationResult 6 | import com.clouway.oauth2.authorization.ClientAuthorizer 7 | import com.clouway.oauth2.authorization.FindAuthorizationResult 8 | import com.clouway.oauth2.client.ClientFinder 9 | import com.clouway.oauth2.common.DateTime 10 | import com.clouway.oauth2.token.UrlSafeTokenGenerator 11 | import java.util.concurrent.ConcurrentHashMap 12 | 13 | /** 14 | * @author Ivan Stefanov @clouway.com> 15 | */ 16 | internal class InMemoryClientAuthorizer(private val clientFinder: ClientFinder) : ClientAuthorizer { 17 | private val authorizations: MutableMap = ConcurrentHashMap() 18 | override fun findAuthorization(clientId: String, authCode: String, instant: DateTime): FindAuthorizationResult { 19 | val authorization = authorizations[authCode] 20 | ?: return FindAuthorizationResult.NotFound() 21 | // No authorization was found for that code ? 22 | val possibleClient = clientFinder.findClient(clientId) 23 | if (!possibleClient.isPresent) { 24 | return FindAuthorizationResult.ClientNotFound() 25 | } 26 | authorizations.remove(authCode) 27 | return FindAuthorizationResult.Success(authorization, possibleClient.get()) 28 | } 29 | 30 | private fun register(authorization: Authorization) { 31 | authorizations[authorization.code] = authorization 32 | } 33 | 34 | 35 | override fun authorizeClient(req: AuthorizationRequest): ClientAuthorizationResult { 36 | val possibleClient = clientFinder.findClient(req.clientId) 37 | if (!possibleClient.isPresent) { 38 | return ClientAuthorizationResult.ClientNotFound() 39 | } 40 | 41 | val code = UrlSafeTokenGenerator().generate() 42 | val client = possibleClient.get() 43 | 44 | val auth = Authorization( 45 | responseType = req.responseType, 46 | clientId = req.clientId, 47 | identityId = req.identityId, 48 | code = code, 49 | scopes = req.scopes, 50 | redirectUrls = client.redirectURLs, 51 | codeChallenge = req.codeChallenge, 52 | params = req.params 53 | ) 54 | 55 | authorizations[code] = auth 56 | 57 | return ClientAuthorizationResult.Success(client, code) 58 | } 59 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/storage/InMemoryClientRepository.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.storage; 2 | 3 | import com.clouway.oauth2.client.Client; 4 | import com.clouway.oauth2.client.ClientFinder; 5 | import com.clouway.oauth2.client.ClientRegistrationRequest; 6 | import com.clouway.oauth2.client.JwtKeyStore; 7 | import com.clouway.oauth2.exampleapp.ClientRegistry; 8 | import com.clouway.oauth2.jws.Pem; 9 | import com.clouway.oauth2.jws.Pem.Block; 10 | import com.clouway.oauth2.jwt.Jwt.ClaimSet; 11 | import com.clouway.oauth2.jwt.Jwt.Header; 12 | import com.clouway.oauth2.token.TokenGenerator; 13 | import com.google.common.base.Optional; 14 | import com.google.common.collect.Maps; 15 | import com.google.inject.Inject; 16 | 17 | import java.io.ByteArrayInputStream; 18 | import java.io.IOException; 19 | import java.util.Map; 20 | import java.util.UUID; 21 | 22 | /** 23 | * @author Ivan Stefanov 24 | */ 25 | class InMemoryClientRepository implements ClientRegistry, ClientFinder, JwtKeyStore { 26 | private final Map clients = Maps.newHashMap(); 27 | private final Map serviceAccountKeys = Maps.newHashMap(); 28 | private final Pem pem = new Pem(); 29 | private final TokenGenerator tokenGenerator; 30 | 31 | @Inject 32 | public InMemoryClientRepository(TokenGenerator tokenGenerator) { 33 | this.tokenGenerator = tokenGenerator; 34 | } 35 | 36 | @Override 37 | public Client register(Client client) { 38 | clients.put(client.id, client); 39 | return client; 40 | } 41 | 42 | @Override 43 | public Client register(ClientRegistrationRequest request) { 44 | String randomId = UUID.randomUUID().toString(); 45 | Client client = 46 | new Client(randomId, request.secret, request.description, request.redirectURLs, false); 47 | clients.put(client.id, client); 48 | return client; 49 | } 50 | 51 | @Override 52 | public Optional findClient(String clientId) { 53 | return Optional.fromNullable(clients.get(clientId)); 54 | } 55 | 56 | @Override 57 | public Optional findKey(Header header, ClaimSet claimSet) { 58 | return Optional.fromNullable(serviceAccountKeys.get(claimSet.iss)); 59 | } 60 | 61 | public void registerServiceAccount(String clientEmail, String privateKeyAsPem) { 62 | try { 63 | serviceAccountKeys.put(clientEmail, pem.parse(new ByteArrayInputStream(privateKeyAsPem.getBytes()))); 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | 69 | 70 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/storage/InMemoryResourceOwnerRepository.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.storage 2 | 3 | import com.clouway.oauth2.Session 4 | import com.clouway.oauth2.exampleapp.ResourceOwner 5 | import com.clouway.oauth2.exampleapp.ResourceOwnerAuthentication 6 | import com.clouway.oauth2.exampleapp.ResourceOwnerStore 7 | import com.clouway.oauth2.exampleapp.SessionSecurity 8 | import com.clouway.oauth2.token.TokenGenerator 9 | import com.google.common.base.Optional 10 | import com.google.inject.Inject 11 | 12 | /** 13 | * @author Ivan Stefanov @clouway.com> 14 | */ 15 | internal class InMemoryResourceOwnerRepository 16 | @Inject 17 | constructor( 18 | private val tokenGenerator: TokenGenerator, 19 | ) : ResourceOwnerStore, 20 | ResourceOwnerAuthentication, 21 | SessionSecurity { 22 | private val resourceOwners = mutableMapOf("admin" to ResourceOwner("admin", "admin")) 23 | 24 | private val sessions: MutableSet = HashSet() 25 | 26 | override fun save(resourceOwner: ResourceOwner) { 27 | resourceOwners[resourceOwner.username] = resourceOwner 28 | } 29 | 30 | override fun auth( 31 | username: String, 32 | password: String, 33 | remoteAddress: String, 34 | ): Optional { 35 | if (resourceOwners.containsKey(username)) { 36 | if (password == resourceOwners[username]!!.password) { 37 | val session = Session(tokenGenerator.generate()) 38 | 39 | sessions.add(session) 40 | 41 | return Optional.of(session) 42 | } 43 | } 44 | 45 | return Optional.absent() 46 | } 47 | 48 | override fun exists(session: Session): Boolean = sessions.contains(session) 49 | } 50 | -------------------------------------------------------------------------------- /oauth2-example-app/src/main/java/com/clouway/oauth2/exampleapp/storage/InMemoryUserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.storage 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.oauth2.ResourceOwnerIdentityFinder 5 | import com.clouway.oauth2.common.DateTime 6 | import com.clouway.oauth2.exampleapp.UserRepository 7 | import com.clouway.oauth2.token.FindIdentityRequest 8 | import com.clouway.oauth2.token.FindIdentityResult 9 | import com.clouway.oauth2.token.GrantType 10 | import com.clouway.oauth2.token.Identity 11 | import com.clouway.oauth2.token.IdentityFinder 12 | import com.clouway.oauth2.token.ServiceAccount 13 | import com.clouway.oauth2.token.User 14 | import com.google.common.base.Optional 15 | 16 | /** 17 | * @author Mihail Lesikov (mlesikov@gmail.com) 18 | */ 19 | internal class InMemoryUserRepository : 20 | IdentityFinder, 21 | ResourceOwnerIdentityFinder, 22 | UserRepository { 23 | override fun find( 24 | request: Request, 25 | instantTime: DateTime, 26 | ): Optional { 27 | // get session id from cookie 28 | // and retrieve user information for that SID 29 | for (sid in request.cookie("SID")) { 30 | return Optional.of("testUserID") 31 | } 32 | 33 | return Optional.absent() 34 | } 35 | 36 | override fun findIdentity(request: FindIdentityRequest): FindIdentityResult = 37 | if (request.grantType == GrantType.AUTHORIZATION_CODE) { 38 | FindIdentityResult.User( 39 | Identity( 40 | id = "testUserID", 41 | name = "testUser", 42 | givenName = "test User", 43 | familyName = "User Family", 44 | email = "test@clouway.com", 45 | picture = null, 46 | claims = emptyMap(), 47 | ), 48 | ) 49 | } else { 50 | FindIdentityResult.ServiceAccountClient( 51 | ServiceAccount( 52 | clientId = "customerapp@testapp.com", 53 | clientEmail = "testapp@testapp.com", 54 | name = "Test App", 55 | customerId = null, 56 | claims = emptyMap(), 57 | ), 58 | ) 59 | } 60 | 61 | override fun load(userId: String): Optional = Optional.of(User("testUserID", "test@clouway.com", "Ivan Stefanov")) 62 | } 63 | -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/SessionEqualityTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.oauth2.Session; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.Matchers.is; 7 | import static org.hamcrest.Matchers.not; 8 | import static org.junit.Assert.assertThat; 9 | 10 | /** 11 | * @author Ivan Stefanov 12 | */ 13 | public class SessionEqualityTest { 14 | @Test 15 | public void areEqual() { 16 | Session session1 = new Session("session"); 17 | Session session2 = new Session("session"); 18 | 19 | assertThat(session1, is(session2)); 20 | } 21 | 22 | @Test 23 | public void areNotEqual() { 24 | Session session1 = new Session("session1"); 25 | Session session2 = new Session("session2"); 26 | 27 | assertThat(session1, is(not(session2))); 28 | } 29 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/exampleapp/ClientRegistryContractTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.clouway.oauth2.client.Client; 4 | import com.clouway.oauth2.client.ClientRegistrationRequest; 5 | import com.clouway.oauth2.exampleapp.storage.InMemoryClientRepositoryTest; 6 | import com.google.common.base.Optional; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import java.util.Collections; 11 | 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.hamcrest.core.Is.is; 14 | import static org.junit.Assert.assertFalse; 15 | 16 | /** 17 | * @author Mihail Lesikov (mlesikov@gmail.com) 18 | */ 19 | public class ClientRegistryContractTest { 20 | 21 | private InMemoryClientRepositoryTest repository; 22 | 23 | @Before 24 | public void setUp() throws Exception { 25 | repository = new InMemoryClientRepositoryTest(); 26 | } 27 | 28 | @Test 29 | public void registerClientByClientObject() throws Exception { 30 | Client client = new Client("id", "secrets", "description", Collections.emptySet(), true); 31 | repository.register(client); 32 | 33 | Optional actualClient = repository.findClient("id"); 34 | 35 | assertThat(actualClient.get(), is(client)); 36 | } 37 | 38 | @Test 39 | public void registerClientByRegistrationRequest() throws Exception { 40 | ClientRegistrationRequest request = new ClientRegistrationRequest("secret", "description", Collections.emptySet()); 41 | Client client = repository.register(request); 42 | 43 | Optional actualClient = repository.findClient(client.id); 44 | 45 | assertThat(actualClient.get(), is(client)); 46 | } 47 | 48 | @Test 49 | public void findById() throws Exception { 50 | ClientRegistrationRequest request = new ClientRegistrationRequest("secret1", "description1", Collections.singleton("redirectURI1")); 51 | Client client = repository.register(request); 52 | 53 | Optional actualClient = repository.findClient(client.id); 54 | 55 | assertThat(actualClient.get(), is(client)); 56 | } 57 | 58 | @Test 59 | public void notExistingId() throws Exception { 60 | Optional client = repository.findClient("id2"); 61 | 62 | assertFalse(client.isPresent()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/exampleapp/ErrorResponseDTOEqualityTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.Matchers.is; 6 | import static org.hamcrest.Matchers.not; 7 | import static org.junit.Assert.assertThat; 8 | 9 | /** 10 | * @author Ivan Stefanov 11 | */ 12 | public class ErrorResponseDTOEqualityTest { 13 | @Test 14 | public void areEqual() { 15 | ErrorResponseDTO errorResponse1 = new ErrorResponseDTO("code", "description"); 16 | ErrorResponseDTO errorResponse2 = new ErrorResponseDTO("code", "description"); 17 | 18 | assertThat(errorResponse1, is(errorResponse2)); 19 | } 20 | 21 | @Test 22 | public void areNotEqual() { 23 | ErrorResponseDTO errorResponse1 = new ErrorResponseDTO("code1", "description1"); 24 | ErrorResponseDTO errorResponse2 = new ErrorResponseDTO("code2", "description2"); 25 | 26 | assertThat(errorResponse1, is(not(errorResponse2))); 27 | } 28 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/exampleapp/LoginEndpointTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.clouway.oauth2.Session; 4 | import com.google.common.base.Optional; 5 | import org.jmock.Expectations; 6 | import org.jmock.auto.Mock; 7 | import org.jmock.integration.junit4.JUnitRuleMockery; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | 11 | import javax.servlet.http.Cookie; 12 | import javax.servlet.http.HttpServletRequest; 13 | 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.hamcrest.core.Is.is; 16 | import static org.junit.Assert.assertTrue; 17 | 18 | 19 | /** 20 | * @author Ivan Stefanov 21 | */ 22 | public class LoginEndpointTest { 23 | private final String remoteAddress = "123.2.2.2"; 24 | @Rule 25 | public JUnitRuleMockery context = new JUnitRuleMockery(); 26 | 27 | private ResourceOwnerAuthentication authentication = context.mock(ResourceOwnerAuthentication.class); 28 | private FakeHttpServletResponse response = new FakeHttpServletResponse(); 29 | 30 | private LoginEndpoint endpoint = new LoginEndpoint(authentication); 31 | 32 | @Mock 33 | HttpServletRequest request; 34 | 35 | @Test 36 | public void happyPath() throws Exception { 37 | final Session session = new Session("session_value"); 38 | final Cookie cookie = new Cookie("SID", session.value); 39 | 40 | context.checking(new Expectations() {{ 41 | oneOf(request).getRemoteAddr(); 42 | will(Expectations.returnValue(remoteAddress)); 43 | oneOf(request).getParameter("continue"); 44 | will(returnValue("/movies")); 45 | oneOf(authentication).auth("FeNoMeNa", "parola", remoteAddress); 46 | will(Expectations.returnValue(Optional.of(session))); 47 | }}); 48 | 49 | pretendThatUserIsAuthorisedWith("FeNoMeNa", "parola"); 50 | 51 | String redirectPage = endpoint.login(request,response); 52 | 53 | response.assertContains(cookie); 54 | assertThat(redirectPage, is("/movies")); 55 | } 56 | 57 | @Test 58 | public void wrongAuthentication() throws Exception { 59 | context.checking(new Expectations() {{ 60 | oneOf(request).getRemoteAddr(); 61 | will(Expectations.returnValue(remoteAddress)); 62 | oneOf(request).getParameter("continue"); 63 | will(returnValue("/xxx")); 64 | oneOf(request).getRequestURI(); 65 | oneOf(authentication).auth("Grigor", "dimitrov", remoteAddress); 66 | will(Expectations.returnValue(Optional.absent())); 67 | }}); 68 | 69 | pretendThatUserIsAuthorisedWith("Grigor", "dimitrov"); 70 | 71 | String redirectPage = endpoint.login(request, response); 72 | 73 | response.assertDoesNotExist("SID"); 74 | assertTrue(redirectPage.contains("?continue")); 75 | assertTrue(redirectPage.endsWith("&errormessage=Invalid+username+or+password")); 76 | } 77 | 78 | private void pretendThatUserIsAuthorisedWith(String username, String password) { 79 | endpoint.setUsername(username); 80 | endpoint.setPassword(password); 81 | } 82 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/exampleapp/RegistrationResponseDTOEqualityTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.Matchers.is; 6 | import static org.hamcrest.Matchers.not; 7 | import static org.junit.Assert.assertThat; 8 | 9 | /** 10 | * @author Ivan Stefanov 11 | */ 12 | public class RegistrationResponseDTOEqualityTest { 13 | @Test 14 | public void areEqual() { 15 | RegistrationResponseDTO response1 = new RegistrationResponseDTO("clientId", "secret"); 16 | RegistrationResponseDTO response2 = new RegistrationResponseDTO("clientId", "secret"); 17 | 18 | assertThat(response1, is(response2)); 19 | } 20 | 21 | @Test 22 | public void areNotEqual() { 23 | RegistrationResponseDTO response1 = new RegistrationResponseDTO("id1", "secret1"); 24 | RegistrationResponseDTO response2 = new RegistrationResponseDTO("id2", "secret2"); 25 | 26 | assertThat(response1, is(not(response2))); 27 | } 28 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/exampleapp/ReplyMatchers.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.google.sitebricks.headless.Reply; 4 | import org.hamcrest.Description; 5 | import org.hamcrest.Matcher; 6 | import org.hamcrest.TypeSafeMatcher; 7 | 8 | import java.lang.reflect.Field; 9 | 10 | import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED; 11 | import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; 12 | import static javax.servlet.http.HttpServletResponse.SC_CREATED; 13 | import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 14 | import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; 15 | import static javax.servlet.http.HttpServletResponse.SC_OK; 16 | import static javax.servlet.http.HttpServletResponse.SC_RESET_CONTENT; 17 | import static org.junit.Assert.fail; 18 | 19 | /** 20 | * @author Miroslav Genov (mgenov@gmail.com) 21 | */ 22 | public class ReplyMatchers { 23 | 24 | public static Matcher> containsValue(final Object value) { 25 | return contains(value); 26 | } 27 | 28 | public static Matcher> contains(final Object value) { 29 | return new TypeSafeMatcher>() { 30 | @Override 31 | public boolean matchesSafely(Reply reply) { 32 | Object responseValue = property("entity", reply); 33 | return value.equals(responseValue); 34 | } 35 | 36 | public void describeTo(Description description) { 37 | description.appendText("reply value was different from expected one"); 38 | } 39 | }; 40 | } 41 | 42 | public static Matcher> redirectUriIs(final String uri) { 43 | return new TypeSafeMatcher>() { 44 | @Override 45 | public boolean matchesSafely(Reply reply) { 46 | Object responseValue = property("redirectUri", reply); 47 | return uri.equals(responseValue); 48 | } 49 | 50 | public void describeTo(Description description) { 51 | description.appendText("reply redirect uri is different from the expected one"); 52 | } 53 | }; 54 | } 55 | 56 | public static Matcher> isOk() { 57 | return returnCodeMatcher(SC_OK); 58 | } 59 | 60 | public static Matcher> isCreated(){ 61 | return returnCodeMatcher(SC_CREATED); 62 | } 63 | 64 | public static Matcher> isAccepted(){ 65 | return returnCodeMatcher(SC_ACCEPTED); 66 | } 67 | 68 | public static Matcher> isResetContent() { 69 | return returnCodeMatcher(SC_RESET_CONTENT); 70 | } 71 | 72 | public static Matcher> isBadRequest() { 73 | return returnCodeMatcher(SC_BAD_REQUEST); 74 | } 75 | 76 | public static Matcher> isNotFound() { 77 | return returnCodeMatcher(SC_NOT_FOUND); 78 | } 79 | 80 | public static Matcher> isInternalServerError() { 81 | return returnCodeMatcher(SC_INTERNAL_SERVER_ERROR); 82 | } 83 | 84 | private static Matcher> returnCodeMatcher(final int expectedCode) { 85 | return new TypeSafeMatcher>() { 86 | @Override 87 | public boolean matchesSafely(Reply reply) { 88 | Integer status = property("status", reply); 89 | return Integer.valueOf(expectedCode).equals(status); 90 | } 91 | 92 | public void describeTo(Description description) { 93 | description.appendText("status of the replay was different from expected"); 94 | } 95 | }; 96 | } 97 | 98 | private static T property(String fieldName, Reply reply) { 99 | Field field = null; 100 | try { 101 | field = reply.getClass().getDeclaredField(fieldName); 102 | field.setAccessible(true); 103 | T actual = (T) field.get(reply); 104 | 105 | return actual; 106 | } catch (NoSuchFieldException e) { 107 | fail(e.getMessage()); 108 | } catch (IllegalAccessException e) { 109 | fail(e.getMessage()); 110 | } finally { 111 | if (field != null) { 112 | field.setAccessible(false); 113 | } 114 | } 115 | return null; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/exampleapp/SiteBricksRequestMockery.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp; 2 | 3 | import com.google.common.collect.Multimap; 4 | import com.google.inject.TypeLiteral; 5 | import com.google.sitebricks.client.Transport; 6 | import com.google.sitebricks.headless.Request; 7 | 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | 11 | /** 12 | * @author Adelin Ghanayem adelin.ghanaem@clouway.com 13 | */ 14 | public class SiteBricksRequestMockery { 15 | 16 | public static Request mockRequest(final E request) { 17 | return new Request() { 18 | private E e = request; 19 | 20 | @Override 21 | public RequestRead read(Class type) { 22 | return new RequestRead() { 23 | @Override 24 | public E as(Class transport) { 25 | return (E) request; 26 | } 27 | }; 28 | } 29 | 30 | @Override 31 | public RequestRead read(TypeLiteral type) { 32 | return null; 33 | } 34 | 35 | @Override 36 | public void readTo(OutputStream out) throws IOException { 37 | if (e instanceof String) { 38 | out.write(((String) e).getBytes()); 39 | } else { 40 | throw new IllegalStateException("Try to test with some string ! "); 41 | } 42 | } 43 | 44 | @Override 45 | public Multimap headers() { 46 | return null; 47 | } 48 | 49 | @Override 50 | public Multimap params() { 51 | return null; 52 | } 53 | 54 | @Override 55 | public Multimap matrix() { 56 | return null; 57 | } 58 | 59 | @Override 60 | public String matrixParam(String name) { 61 | return null; 62 | } 63 | 64 | @Override 65 | public String param(String name) { 66 | return null; 67 | } 68 | 69 | @Override 70 | public String header(String name) { 71 | return null; 72 | } 73 | 74 | @Override 75 | public String uri() { 76 | return null; 77 | } 78 | 79 | @Override 80 | public String path() { 81 | return null; 82 | } 83 | 84 | @Override 85 | public String context() { 86 | return null; 87 | } 88 | 89 | @Override 90 | public String method() { 91 | return null; 92 | } 93 | }; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/exampleapp/security/OAuthSecurityFilterTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.security; 2 | 3 | import com.clouway.oauth2.Session; 4 | import com.clouway.oauth2.exampleapp.SessionSecurity; 5 | import com.clouway.oauth2.exampleapp.FakeHttpServletRequest; 6 | import com.clouway.oauth2.exampleapp.FakeHttpServletResponse; 7 | import com.google.common.collect.Sets; 8 | import org.jmock.Expectations; 9 | import org.jmock.integration.junit4.JUnitRuleMockery; 10 | import org.junit.Rule; 11 | import org.junit.Test; 12 | 13 | import javax.servlet.FilterChain; 14 | import javax.servlet.http.Cookie; 15 | 16 | /** 17 | * @author Ivan Stefanov 18 | */ 19 | public class OAuthSecurityFilterTest { 20 | @Rule 21 | public JUnitRuleMockery context = new JUnitRuleMockery(); 22 | 23 | private SessionSecurity sessionSecurity = context.mock(SessionSecurity.class); 24 | private FilterChain chain = context.mock(FilterChain.class); 25 | 26 | private SecuredResources securedResources = createSecuredResources("/auth", "/xxx", "movies"); 27 | 28 | private OAuthSecurityFilter filter = new OAuthSecurityFilter(sessionSecurity, securedResources,"", ""); 29 | 30 | @Test 31 | public void happyPath() throws Exception { 32 | final Session session = new Session("token123"); 33 | final FakeHttpServletRequest servletRequest = new FakeHttpServletRequest("/auth", authCookie(session.value)); 34 | final FakeHttpServletResponse servletResponse = new FakeHttpServletResponse(); 35 | 36 | context.checking(new Expectations() {{ 37 | oneOf(sessionSecurity).exists(session); 38 | will(Expectations.returnValue(true)); 39 | 40 | oneOf(chain).doFilter(servletRequest, servletResponse); 41 | }}); 42 | 43 | filter.doFilter(servletRequest, servletResponse, chain); 44 | } 45 | 46 | @Test 47 | public void invalidSession() throws Exception { 48 | final Session session = new Session("123token"); 49 | final FakeHttpServletRequest servletRequest = new FakeHttpServletRequest("/xxx?redirect_uri=http://abv.bg/", authCookie(session.value)); 50 | final FakeHttpServletResponse servletResponse = new FakeHttpServletResponse(); 51 | 52 | context.checking(new Expectations() {{ 53 | oneOf(sessionSecurity).exists(session); 54 | will(Expectations.returnValue(false)); 55 | }}); 56 | 57 | filter.doFilter(servletRequest, servletResponse, chain); 58 | 59 | servletResponse.assertRedirectTo("/login?redirectUrl=%2Fxxx%3Fredirect_uri%3Dhttp%3A%2F%2Fabv.bg%2F"); 60 | } 61 | 62 | @Test 63 | public void invalidSessionRedirectToThLoginPath() throws Exception { 64 | final Session session = new Session("123token"); 65 | final FakeHttpServletRequest servletRequest = new FakeHttpServletRequest("/xxx?redirect_uri=http://abv.bg/", authCookie(session.value)); 66 | final FakeHttpServletResponse servletResponse = new FakeHttpServletResponse(); 67 | 68 | context.checking(new Expectations() {{ 69 | oneOf(sessionSecurity).exists(session); 70 | will(Expectations.returnValue(false)); 71 | }}); 72 | 73 | filter = new OAuthSecurityFilter(sessionSecurity,securedResources, "", "/loginPagePath"); 74 | filter.doFilter(servletRequest, servletResponse, chain); 75 | 76 | servletResponse.assertRedirectTo("/loginPagePath?redirectUrl=%2Fxxx%3Fredirect_uri%3Dhttp%3A%2F%2Fabv.bg%2F"); 77 | } 78 | 79 | 80 | @Test 81 | public void unsecuredResource() throws Exception { 82 | final FakeHttpServletRequest servletRequest = new FakeHttpServletRequest("/login", authCookie("token")); 83 | final FakeHttpServletResponse servletResponse = new FakeHttpServletResponse(); 84 | 85 | context.checking(new Expectations() {{ 86 | oneOf(chain).doFilter(servletRequest, servletResponse); 87 | }}); 88 | 89 | filter.doFilter(servletRequest, servletResponse, chain); 90 | } 91 | 92 | private SecuredResources createSecuredResources(final String... resources) { 93 | return new SecuredResources(Sets.newHashSet(resources)); 94 | } 95 | 96 | private Cookie[] authCookie(String value) { 97 | return new Cookie[] {new Cookie("SID", value)}; 98 | } 99 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/exampleapp/security/SecuredResourcesTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.security; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.junit.Test; 5 | 6 | import java.util.HashSet; 7 | 8 | import static org.junit.Assert.assertFalse; 9 | import static org.junit.Assert.assertTrue; 10 | 11 | /** 12 | * @author Ivan Stefanov 13 | */ 14 | public class SecuredResourcesTest { 15 | private SecuredResources resources; 16 | 17 | @Test 18 | public void checkExistingResource() throws Exception { 19 | resources = new SecuredResources(Sets.newHashSet("/login","/register")); 20 | assertTrue(resources.contains("/register")); 21 | } 22 | 23 | @Test 24 | public void checkNotExistingResource() throws Exception { 25 | resources = new SecuredResources(new HashSet()); 26 | assertFalse(resources.contains("/login")); 27 | } 28 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/exampleapp/storage/InMemoryClientRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.storage; 2 | 3 | import com.clouway.oauth2.client.Client; 4 | import com.clouway.oauth2.client.ClientFinder; 5 | import com.clouway.oauth2.client.ClientRegistrationRequest; 6 | import com.clouway.oauth2.exampleapp.ClientRegistry; 7 | import com.google.common.base.Optional; 8 | 9 | /** 10 | * @author Ivan Stefanov 11 | */ 12 | public class InMemoryClientRepositoryTest implements ClientFinder, ClientRegistry { 13 | 14 | private InMemoryClientRepository repository = new InMemoryClientRepository(); 15 | 16 | @Override 17 | public Client register(Client client) { 18 | return repository.register(client); 19 | } 20 | 21 | @Override 22 | public Client register(ClientRegistrationRequest request) { 23 | return repository.register(request); 24 | } 25 | 26 | @Override 27 | public Optional findClient(String clientId) { 28 | return repository.findClient(clientId); 29 | } 30 | } -------------------------------------------------------------------------------- /oauth2-example-app/src/test/java/com/clouway/oauth2/exampleapp/storage/InMemoryResourceOwnerRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.exampleapp.storage; 2 | 3 | import com.clouway.oauth2.exampleapp.ResourceOwner; 4 | import com.clouway.oauth2.Session; 5 | import com.clouway.oauth2.token.TokenGenerator; 6 | import com.google.common.base.Optional; 7 | import org.jmock.Expectations; 8 | import org.jmock.integration.junit4.JUnitRuleMockery; 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.hamcrest.core.Is.is; 14 | import static org.junit.Assert.assertFalse; 15 | import static org.junit.Assert.assertTrue; 16 | 17 | /** 18 | * @author Ivan Stefanov 19 | */ 20 | public class InMemoryResourceOwnerRepositoryTest { 21 | @Rule 22 | public JUnitRuleMockery context = new JUnitRuleMockery(); 23 | 24 | private TokenGenerator tokenGenerator = context.mock(TokenGenerator.class); 25 | 26 | private InMemoryResourceOwnerRepository repository = new InMemoryResourceOwnerRepository(tokenGenerator); 27 | private String remoteAddress= "123.1.1.1"; 28 | 29 | @Test 30 | public void correctCredentials() throws Exception { 31 | ResourceOwner owner = new ResourceOwner("username", "password"); 32 | 33 | context.checking(new Expectations() {{ 34 | oneOf(tokenGenerator).generate(); 35 | will(returnValue("token1234")); 36 | }}); 37 | 38 | repository.save(owner); 39 | 40 | Optional session = repository.auth(owner.username, owner.password, remoteAddress); 41 | 42 | assertThat(session.get().value, is("token1234")); 43 | } 44 | 45 | @Test 46 | public void notExistingUser() throws Exception { 47 | Optional session = repository.auth("joro", "123456", remoteAddress); 48 | 49 | assertFalse(session.isPresent()); 50 | } 51 | 52 | @Test 53 | public void wrongPassword() throws Exception { 54 | ResourceOwner owner = new ResourceOwner("username", "pass"); 55 | 56 | repository.save(owner); 57 | 58 | Optional session = repository.auth("username", "123456", remoteAddress); 59 | 60 | assertFalse(session.isPresent()); 61 | } 62 | 63 | @Test 64 | public void existingSession() throws Exception { 65 | final ResourceOwner owner = new ResourceOwner("Ivan", "password"); 66 | final Session session = new Session("token4321"); 67 | 68 | context.checking(new Expectations() {{ 69 | oneOf(tokenGenerator).generate(); 70 | will(returnValue(session.value)); 71 | }}); 72 | 73 | repository.save(owner); 74 | repository.auth(owner.username, owner.password, remoteAddress); 75 | 76 | Boolean sessionIsPresented = repository.exists(session); 77 | 78 | assertTrue(sessionIsPresented); 79 | } 80 | 81 | @Test 82 | public void notExistingSession() throws Exception { 83 | Session session = new Session("session123"); 84 | 85 | Boolean sessionIsPresented = repository.exists(session); 86 | 87 | assertFalse(sessionIsPresented); 88 | } 89 | } -------------------------------------------------------------------------------- /oauth2-server/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary", "kt_jvm_library") 2 | load("@rules_java//java:defs.bzl", "java_binary", "java_import", "java_library") 3 | load("@rules_jvm_external//:defs.bzl", "java_export") 4 | load("@io_bazel_rules_kotlin//third_party:jarjar.bzl", "jar_jar") 5 | 6 | package(default_visibility = ["//visibility:public"]) 7 | 8 | exports_files( 9 | ["shade.jarjar"], 10 | ) 11 | 12 | java_binary( 13 | name = "provided-deps", 14 | create_executable = False, 15 | runtime_deps = [ 16 | "@com_github_jetbrains_kotlin//:annotations", 17 | "@com_github_jetbrains_kotlin//:jvm-abi-gen", 18 | "@com_github_jetbrains_kotlin//:kotlin-reflect", 19 | "@com_github_jetbrains_kotlin//:kotlin-stdlib", 20 | "@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk7", 21 | "@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk8", 22 | "@maven//:com_google_code_gson_gson", 23 | "@maven//:com_google_guava_guava", 24 | "@maven//:javax_annotation_javax_annotation_api", 25 | "@maven//:javax_servlet_servlet_api", 26 | ], 27 | ) 28 | 29 | # A helper task used to bundle all classes 30 | # as a single jar to be provided to jarjar. 31 | java_binary( 32 | name = "binaryjar", 33 | create_executable = False, 34 | deploy_env = [ 35 | ":provided-deps", 36 | ], 37 | runtime_deps = [ 38 | "//oauth2-server/src/main/java/com/clouway/oauth2", 39 | "//oauth2-server/src/main/java/com/clouway/oauth2/authorization", 40 | "//oauth2-server/src/main/java/com/clouway/oauth2/client", 41 | "//oauth2-server/src/main/java/com/clouway/oauth2/codechallenge", 42 | "//oauth2-server/src/main/java/com/clouway/oauth2/common", 43 | "//oauth2-server/src/main/java/com/clouway/oauth2/jws", 44 | "//oauth2-server/src/main/java/com/clouway/oauth2/jwt", 45 | "//oauth2-server/src/main/java/com/clouway/oauth2/keystore", 46 | "//oauth2-server/src/main/java/com/clouway/oauth2/token", 47 | "//oauth2-server/src/main/java/com/clouway/oauth2/util", 48 | ], 49 | ) 50 | 51 | jar_jar( 52 | name = "jarjar", 53 | # Implicit deploy task is added from java_binary 54 | # as kt_jvm_binary is not exporting such task 55 | input_jar = ":binaryjar_deploy.jar", 56 | rules = "shade.jarjar", 57 | ) 58 | 59 | java_export( 60 | name = "exported_lib", 61 | maven_coordinates = "com.clouway.security:oauth2-server:2.0.10-SNAPSHOT", 62 | runtime_deps = [ 63 | ":jarjar", 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /oauth2-server/shade.jarjar: -------------------------------------------------------------------------------- 1 | rule com.fasterxml.jackson.core.** com.clouway.security.internal.jackson.core.@1 2 | rule com.fasterxml.jackson.annotation.** com.clouway.security.internal.jackson.annotations.@1 3 | rule com.fasterxml.jackson.databind.** com.clouway.security.internal.jackson.databind.@1 4 | rule io.jsonwebtoken.** com.clouway.security.internal.jwt.@1 5 | rule com.github.mobiletoly.urlsome.** com.clouway.security.internal.urlsome.@1 -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/AuthCodeAuthorization.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.friendlyserve.Response 5 | import com.clouway.oauth2.authorization.ClientAuthorizer 6 | import com.clouway.oauth2.authorization.FindAuthorizationResult 7 | import com.clouway.oauth2.client.ClientCredentials 8 | import com.clouway.oauth2.common.DateTime 9 | import java.util.logging.Logger 10 | 11 | /** 12 | * @author Vasil Mitov @clouway.com> 13 | */ 14 | internal class AuthCodeAuthorization( 15 | private val clientAuthorizer: ClientAuthorizer, 16 | private val clientActivity: AuthorizedClientActivity 17 | ) : ClientRequest { 18 | 19 | private val logger = Logger.getLogger(AuthCodeAuthorization::class.java.name) 20 | 21 | override fun handleAsOf(request: Request, credentials: ClientCredentials, instant: DateTime): Response { 22 | val authCode = request.param("code") 23 | when (val result = clientAuthorizer.findAuthorization(credentials.clientId(), authCode, instant)) { 24 | is FindAuthorizationResult.Success -> { 25 | 26 | // Make sure that client credentials are matching 27 | // to ensure that the client is same as the one 28 | // that was issued the authorization. 29 | if (result.authorization.clientId != result.client.id) { 30 | logger.info("authorization grant was generated for different client") 31 | return OAuthError.unauthorizedClient("Client credentials not match") 32 | } 33 | 34 | if (!result.client.credentialsMatch(credentials)) { 35 | logger.info("client credentials where not matching") 36 | return OAuthError.unauthorizedClient("Unknown client '${credentials.clientId()}'") 37 | } 38 | return clientActivity.execute(result.authorization, result.client, request, instant) 39 | } 40 | is FindAuthorizationResult.NotFound -> { 41 | return OAuthError.invalidGrant("Authorization was not found.") 42 | } 43 | is FindAuthorizationResult.ClientNotFound -> { 44 | logger.info("client was not found during lookup") 45 | return OAuthError.unauthorizedClient("Unknown client '${credentials.clientId()}'") 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/AuthorizedClientActivity.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.friendlyserve.Response 5 | import com.clouway.oauth2.authorization.Authorization 6 | import com.clouway.oauth2.client.Client 7 | import com.clouway.oauth2.common.DateTime 8 | 9 | /** 10 | * @author Vasil Mitov @clouway.com> 11 | */ 12 | internal interface AuthorizedClientActivity { 13 | fun execute( 14 | authorization: Authorization, 15 | client: Client, 16 | request: Request, 17 | instant: DateTime, 18 | ): Response 19 | } 20 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/AuthorizedIdentityActivity.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.friendlyserve.Response 5 | import com.clouway.oauth2.client.Client 6 | import com.clouway.oauth2.common.DateTime 7 | import com.clouway.oauth2.token.Identity 8 | 9 | /** 10 | * AuthorizedIdentityActivity is an activity which is represents the authorization client. 11 | * 12 | * @author Miroslav Genov (miroslav.genov@clouway.com) 13 | */ 14 | interface AuthorizedIdentityActivity { 15 | /** 16 | * Handles client request of authorized client and returns response. 17 | * 18 | * @param client the client of which request will be handled 19 | * @param request the request 20 | * @param instant the time of which client was requested access 21 | * @param params 22 | * @return the response 23 | */ 24 | fun execute( 25 | client: Client, 26 | identity: Identity, 27 | scopes: Set, 28 | request: Request, 29 | instant: DateTime, 30 | params: Map, 31 | ): Response 32 | } 33 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "oauth2", 7 | srcs = glob([ 8 | "*.java", 9 | "*.kt", 10 | ]), 11 | deps = [ 12 | "//oauth2-server/src/main/java/com/clouway/oauth2/authorization", 13 | "//oauth2-server/src/main/java/com/clouway/oauth2/client", 14 | "//oauth2-server/src/main/java/com/clouway/oauth2/codechallenge", 15 | "//oauth2-server/src/main/java/com/clouway/oauth2/common", 16 | "//oauth2-server/src/main/java/com/clouway/oauth2/jws", 17 | "//oauth2-server/src/main/java/com/clouway/oauth2/jwt", 18 | "//oauth2-server/src/main/java/com/clouway/oauth2/keystore", 19 | "//oauth2-server/src/main/java/com/clouway/oauth2/token", 20 | "//oauth2-server/src/main/java/com/clouway/oauth2/util", 21 | "@maven//:com_clouway_fserve_fserve", 22 | "@maven//:com_fasterxml_jackson_core_jackson_annotations", 23 | "@maven//:com_fasterxml_jackson_core_jackson_core", 24 | "@maven//:com_fasterxml_jackson_core_jackson_databind", 25 | "@maven//:com_github_mobiletoly_urlsome", 26 | "@maven//:com_google_code_gson_gson", 27 | "@maven//:com_google_guava_guava", 28 | "@maven//:io_jsonwebtoken_jjwt_api", 29 | "@maven//:io_jsonwebtoken_jjwt_impl", 30 | "@maven//:io_jsonwebtoken_jjwt_jackson", 31 | "@maven//:javax_annotation_javax_annotation_api", 32 | "@maven//:javax_servlet_servlet_api", 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/BearerTokenResponse.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.RsJson; 4 | import com.clouway.friendlyserve.RsWrap; 5 | import com.google.common.base.Joiner; 6 | import com.google.common.base.Strings; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | 10 | import java.util.Set; 11 | 12 | /** 13 | * BearerTokenResponse is representing the response which is returned by the OAuth Server when token is generated. 14 | *

15 | * Bearer Token Usage is described in RFC6750 16 | * 17 | * @author Miroslav Genov (miroslav.genov@clouway.com) 18 | */ 19 | public class BearerTokenResponse extends RsWrap { 20 | 21 | public BearerTokenResponse(String accessToken, Long expiresInSeconds, Set scopes, String refreshToken, String encodedIdToken) { 22 | super(new RsJson(createToken(accessToken, expiresInSeconds, scopes, refreshToken, encodedIdToken))); 23 | } 24 | 25 | public BearerTokenResponse(String accessToken, Long expiresInSeconds, Set scopes, String refreshToken) { 26 | this(accessToken, expiresInSeconds, scopes, refreshToken, ""); 27 | } 28 | 29 | private static JsonElement createToken(String accessToken, Long expiresInSeconds, Set scopes, String refreshToken, String encodedIdToken) { 30 | JsonObject o = new JsonObject(); 31 | o.addProperty("access_token", accessToken); 32 | o.addProperty("token_type", "Bearer"); 33 | o.addProperty("expires_in", expiresInSeconds); 34 | 35 | if (!scopes.isEmpty()) { 36 | o.addProperty("scope", Joiner.on(" ").join(scopes)); 37 | } 38 | 39 | o.addProperty("refresh_token", refreshToken); 40 | 41 | if (!Strings.isNullOrEmpty(encodedIdToken)) { 42 | o.addProperty("id_token", encodedIdToken); 43 | } 44 | 45 | return o; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/ClientActivity.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.oauth2.client.Client; 4 | import com.clouway.friendlyserve.Request; 5 | import com.clouway.friendlyserve.Response; 6 | import com.clouway.oauth2.common.DateTime; 7 | 8 | /** 9 | * ClientActivity is representing Activity for a single client (in the context of OAuth2). 10 | * 11 | * @author Miroslav Genov (miroslav.genov@clouway.com) 12 | */ 13 | public interface ClientActivity { 14 | 15 | /** 16 | * Handles client request and returns response. 17 | * 18 | * @param client the client of which request will be handled 19 | * @param request the request 20 | * @param instant the time of which client was requested access 21 | * @return the response 22 | */ 23 | Response execute(Client client, Request request, DateTime instant); 24 | } 25 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/ClientAuthenticationCredentialsRequest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.clouway.friendlyserve.Response; 5 | import com.clouway.friendlyserve.RsBadRequest; 6 | import com.clouway.oauth2.client.ClientCredentials; 7 | import com.clouway.oauth2.common.DateTime; 8 | import com.google.common.base.Strings; 9 | 10 | import static com.google.common.io.BaseEncoding.base64; 11 | 12 | /** 13 | * ClientAuthenticationCredentialsRequest is using client authentication credentials request which supports 14 | * the following authentication schemes: 15 | * - basic authentication scheme to decode {@link ClientCredentials} from the Basic Authorization header. 16 | * - param authentication of identifying the public clients. 17 | * 18 | * @author Miroslav Genov (miroslav.genov@clouway.com) 19 | * @see OAuth2 Client Authentication 20 | * @see Basic Authentication Scheme 21 | */ 22 | class ClientAuthenticationCredentialsRequest implements InstantaneousRequest { 23 | 24 | private final ClientRequest clientRequest; 25 | 26 | ClientAuthenticationCredentialsRequest(ClientRequest clientRequest) { 27 | this.clientRequest = clientRequest; 28 | } 29 | 30 | @Override 31 | public Response handleAsOf(Request request, DateTime instantTime) { 32 | String authHeader = request.header("Authorization"); 33 | if (authHeader != null && authHeader.startsWith("Basic")) { 34 | String credentialsString = trimLeadingBasicText(authHeader); 35 | 36 | try { 37 | String decoded = new String(base64().decode(credentialsString)); 38 | 39 | ClientCredentials clientCredentials = parseCredentials(decoded); 40 | return clientRequest.handleAsOf(request, clientCredentials, instantTime); 41 | 42 | } catch (IllegalArgumentException e) { 43 | return new RsBadRequest(); 44 | } 45 | } 46 | 47 | String clientId = request.param("client_id"); 48 | String clientSecret = request.param("client_secret"); 49 | 50 | if (Strings.isNullOrEmpty(clientId)) { 51 | return new RsBadRequest(); 52 | } 53 | 54 | if (Strings.isNullOrEmpty(clientSecret)) { 55 | clientSecret = ""; 56 | } 57 | 58 | return clientRequest.handleAsOf(request, new ClientCredentials(clientId, clientSecret), instantTime); 59 | } 60 | 61 | private ClientCredentials parseCredentials(String decodedHeader) { 62 | if (!decodedHeader.contains(":")) { 63 | throw new IllegalArgumentException("Credentials are not separated with ':'"); 64 | } 65 | 66 | String[] credentials = decodedHeader.split(":"); 67 | 68 | String clientId = credentials[0]; 69 | String clientSecret = credentials[1]; 70 | 71 | return new ClientCredentials(clientId, clientSecret); 72 | } 73 | 74 | private String trimLeadingBasicText(String authHeader) { 75 | return authHeader.substring(6); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/ClientAuthorizationActivity.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.friendlyserve.Response 5 | import com.clouway.friendlyserve.RsRedirect 6 | import com.clouway.oauth2.authorization.ClientAuthorizer 7 | import com.clouway.oauth2.authorization.AuthorizationRequest 8 | import com.clouway.oauth2.authorization.ClientAuthorizationResult 9 | import com.clouway.oauth2.codechallenge.CodeChallenge 10 | import com.clouway.oauth2.common.DateTime 11 | import com.clouway.oauth2.util.Params 12 | import com.github.mobiletoly.urlsome.Urlsome 13 | import com.google.common.base.Splitter 14 | import com.google.common.collect.Sets 15 | import java.time.LocalDateTime 16 | import java.time.ZoneOffset 17 | 18 | /** 19 | * @author Miroslav Genov (miroslav.genov@clouway.com) 20 | */ 21 | internal class ClientAuthorizationActivity( 22 | private val clientAuthorizer: ClientAuthorizer 23 | ) : IdentityActivity { 24 | private val params = Params() 25 | override fun execute(identityId: String, request: Request, instantTime: DateTime): Response { 26 | val responseType = request.param("response_type") 27 | val clientId = request.param("client_id") 28 | val requestedUrl = request.param("redirect_uri") 29 | val state = request.param("state") 30 | val scope = if (request.param("scope") == null) "" else request.param("scope") 31 | val codeChallengeValue = if (request.param("code_challenge") == null) "" else request.param("code_challenge") 32 | val codeVerifierMethod = 33 | if (request.param("code_challenge_method") == null) "" else request.param("code_challenge_method") 34 | val codeChallenge = CodeChallenge(codeChallengeValue, codeVerifierMethod) 35 | 36 | val userDefinedParams = params.parse( 37 | request, 38 | "response_type", 39 | "client_id", 40 | "redirect_uri", 41 | "state", 42 | "scope", 43 | "code_challenge", 44 | "code_challenge_method" 45 | ) 46 | val scopes: Set = Sets.newTreeSet(Splitter.on(" ").omitEmptyStrings().split(scope)) 47 | 48 | val result = clientAuthorizer.authorizeClient( 49 | AuthorizationRequest( 50 | clientId, 51 | identityId, 52 | responseType, 53 | scopes, 54 | codeChallenge, 55 | userDefinedParams, 56 | time = instantTime.toLocalDateTime() 57 | ) 58 | ) 59 | 60 | when (result) { 61 | is ClientAuthorizationResult.Success -> { 62 | val possibleRedirectUrl = result.client.determineRedirectUrl(requestedUrl) 63 | if (!possibleRedirectUrl.isPresent) { 64 | return OAuthError.unauthorizedClient("Client Redirect URL is not matching the configured one.") 65 | } 66 | val callbackUrl = Urlsome(possibleRedirectUrl.get())[ 67 | "code" to result.authCode, 68 | "state" to state, 69 | ].toString() 70 | return RsRedirect(callbackUrl) 71 | } 72 | 73 | is ClientAuthorizationResult.ClientNotFound -> { 74 | // The identity provider is not able to determine the client, 75 | // so we tell the caller that it's an unknown or miss-configured. 76 | return OAuthError.unauthorizedClient("Unknown client '$clientId'") 77 | } 78 | is ClientAuthorizationResult.AccessDenied -> { 79 | // RFC-6749 - Section: 4.2.2.1 80 | // The authorization server redirects the user-agent by 81 | // sending the following HTTP response: 82 | // HTTP/1.1 302 Found 83 | // Location: https://client.example.com/cb#error=access_denied&state=xyz 84 | val callbackUrl = Urlsome(requestedUrl)[ 85 | "error" to "access_denied", 86 | "reason" to result.reason, 87 | "state" to state, 88 | ].toString() 89 | return RsRedirect(callbackUrl) 90 | } 91 | is ClientAuthorizationResult.Error -> { 92 | val callbackUrl = Urlsome(requestedUrl)[ 93 | "error" to "internal_error", 94 | "message" to result.message 95 | ].toString() 96 | return RsRedirect(callbackUrl) 97 | } 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/ClientController.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.oauth2.client.Client; 4 | import com.clouway.oauth2.client.ClientFinder; 5 | import com.clouway.friendlyserve.Request; 6 | import com.clouway.friendlyserve.Response; 7 | import com.clouway.oauth2.client.ClientCredentials; 8 | import com.clouway.oauth2.common.DateTime; 9 | import com.google.common.base.Optional; 10 | 11 | /** 12 | * @author Miroslav Genov (miroslav.genov@clouway.com) 13 | */ 14 | class ClientController implements ClientRequest { 15 | 16 | private final ClientFinder clientFinder; 17 | private final ClientActivity clientActivity; 18 | 19 | ClientController(ClientFinder clientFinder, ClientActivity clientActivity) { 20 | this.clientFinder = clientFinder; 21 | this.clientActivity = clientActivity; 22 | } 23 | 24 | @Override 25 | public Response handleAsOf(Request request, ClientCredentials credentials, DateTime instant) { 26 | Optional possibleResponse = clientFinder.findClient(credentials.clientId()); 27 | 28 | // Client was not authorized 29 | if (!possibleResponse.isPresent()) { 30 | return OAuthError.unauthorizedClient(String.format("Unknown client '%s'", credentials.clientId())); 31 | } 32 | 33 | Client client = possibleResponse.get(); 34 | 35 | // Client credentials did not match? 36 | if (!client.credentialsMatch(credentials)) { 37 | return OAuthError.unauthorizedClient(String.format("Unknown client '%s'", credentials.clientId())); 38 | } 39 | 40 | return clientActivity.execute(client, request, instant); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/ClientRequest.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.friendlyserve.Response 5 | import com.clouway.oauth2.client.ClientCredentials 6 | import com.clouway.oauth2.common.DateTime 7 | 8 | /** 9 | * 10 | * @author Miroslav Genov (miroslav.genov@clouway.com) 11 | */ 12 | interface ClientRequest { 13 | fun handleAsOf(request: Request, credentials: ClientCredentials, instant: DateTime): Response 14 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/CodeExchangeVerificationFlow.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.clouway.friendlyserve.Response; 5 | import com.clouway.oauth2.authorization.Authorization; 6 | import com.clouway.oauth2.client.Client; 7 | import com.clouway.oauth2.codechallenge.CodeVerifier; 8 | import com.clouway.oauth2.common.DateTime; 9 | 10 | /** 11 | * @author Miroslav Genov (miroslav.genov@clouway.com) 12 | */ 13 | class CodeExchangeVerificationFlow implements AuthorizedClientActivity { 14 | private final CodeVerifier codeVerifier; 15 | private final AuthorizedClientActivity authorizedClientActivity; 16 | 17 | CodeExchangeVerificationFlow(CodeVerifier codeVerifier, AuthorizedClientActivity authorizedClientActivity) { 18 | this.codeVerifier = codeVerifier; 19 | this.authorizedClientActivity = authorizedClientActivity; 20 | } 21 | 22 | @Override 23 | public Response execute(Authorization authorization, Client client, Request request, DateTime instant) { 24 | String codeVerifierValue = request.param("code_verifier") == null ? "" : request.param("code_verifier"); 25 | 26 | if (!codeVerifier.verify(authorization.codeChallenge, codeVerifierValue)) { 27 | return OAuthError.invalidGrant("code verification failed"); 28 | } 29 | return authorizedClientActivity.execute(authorization, client, request, instant); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/IdentityActivity.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.clouway.friendlyserve.Response; 5 | import com.clouway.oauth2.common.DateTime; 6 | 7 | /** 8 | * IdentityActivity is an activity which is executed for authorised users. 9 | * 10 | * @author Miroslav Genov (miroslav.genov@clouway.com) 11 | */ 12 | interface IdentityActivity { 13 | 14 | Response execute(String identityId, Request request, DateTime instantTime); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/IdentityAuthorizationActivity.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.friendlyserve.Response 5 | import com.clouway.oauth2.authorization.Authorization 6 | import com.clouway.oauth2.client.Client 7 | import com.clouway.oauth2.common.DateTime 8 | import com.clouway.oauth2.token.FindIdentityRequest 9 | import com.clouway.oauth2.token.FindIdentityResult 10 | import com.clouway.oauth2.token.GrantType 11 | import com.clouway.oauth2.token.IdentityFinder 12 | 13 | /** 14 | * @author Vasil Mitov @clouway.com> 15 | */ 16 | class IdentityAuthorizationActivity( 17 | private val identityFinder: IdentityFinder, 18 | private val authorizedIdentityActivity: AuthorizedIdentityActivity, 19 | ) : AuthorizedClientActivity { 20 | override fun execute( 21 | authorization: Authorization, 22 | client: Client, 23 | request: Request, 24 | instant: DateTime, 25 | ): Response { 26 | val findIdentityRequest = 27 | FindIdentityRequest( 28 | authorization.identityId, 29 | GrantType.AUTHORIZATION_CODE, 30 | instant, 31 | authorization.params, 32 | authorization.clientId, 33 | ) 34 | 35 | when (val res = identityFinder.findIdentity(findIdentityRequest)) { 36 | is FindIdentityResult.User -> { 37 | val identity = res.identity 38 | return authorizedIdentityActivity.execute( 39 | client, 40 | identity, 41 | authorization.scopes, 42 | request, 43 | instant, 44 | authorization.params, 45 | ) 46 | } 47 | is FindIdentityResult.ServiceAccountClient -> { 48 | return OAuthError.invalidClient("service accounts cannot be used for authorization_code grant") 49 | } 50 | is FindIdentityResult.NotFound -> { 51 | return OAuthError.invalidGrant("identity was not found") 52 | } 53 | is FindIdentityResult.Error -> { 54 | return OAuthError.internalError() 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/IdentityController.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.friendlyserve.Response 5 | import com.clouway.friendlyserve.RsRedirect 6 | import com.clouway.oauth2.common.DateTime 7 | import java.io.UnsupportedEncodingException 8 | import java.net.URLEncoder 9 | 10 | /** 11 | * IdentityController is responsible for determining the Identity of the caller. Sent request to this controller 12 | * should contain information about the client application and for Granting User (user session is determined from [Request]). 13 | * 14 | * 15 | * 16 | * @author Miroslav Genov (miroslav.genov@clouway.com) 17 | */ 18 | internal class IdentityController( 19 | private val identityFinder: ResourceOwnerIdentityFinder, 20 | private val identityActivity: IdentityActivity, 21 | private val loginPageUrl: String 22 | ) : InstantaneousRequest { 23 | override fun handleAsOf(request: Request, instantTime: DateTime): Response { 24 | val prompt = request.param("prompt") 25 | if (prompt == "consent") { 26 | val continueTo = queryParams(request) 27 | return RsRedirect(loginPageUrl + continueTo) 28 | } 29 | 30 | val optIdentity = identityFinder.find(request, instantTime) 31 | // Browser should be redirected to login page when Identity is not found 32 | if (!optIdentity.isPresent) { 33 | val continueTo = queryParams(request) 34 | return RsRedirect(loginPageUrl + continueTo) 35 | } 36 | return identityActivity.execute(optIdentity.get(), request, instantTime) 37 | } 38 | 39 | private fun queryParams(request: Request): String { 40 | var params = "" 41 | for (param in request.names()) { 42 | if (param == "prompt") { 43 | continue 44 | } 45 | params += "&" + param + "=" + request.param(param) 46 | } 47 | return try { 48 | URLEncoder.encode(request.path() + "?" + params.substring(1, params.length), "UTF-8") 49 | } catch (e: UnsupportedEncodingException) { 50 | "" 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/InstantaneousRequest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.clouway.friendlyserve.Response; 5 | import com.clouway.oauth2.common.DateTime; 6 | 7 | /** 8 | * @author Miroslav Genov (miroslav.genov@clouway.com) 9 | */ 10 | public interface InstantaneousRequest { 11 | 12 | Response handleAsOf(Request request, DateTime instantTime); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/InstantaneousRequestController.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.clouway.friendlyserve.Response; 5 | import com.clouway.friendlyserve.Take; 6 | import com.clouway.oauth2.common.DateTime; 7 | 8 | import java.io.IOException; 9 | 10 | /** 11 | * @author Miroslav Genov (miroslav.genov@clouway.com) 12 | */ 13 | class InstantaneousRequestController implements Take { 14 | 15 | private final InstantaneousRequest instantaneousRequest; 16 | 17 | InstantaneousRequestController(InstantaneousRequest request) { 18 | this.instantaneousRequest = request; 19 | } 20 | 21 | @Override 22 | public Response ack(Request request) throws IOException { 23 | return instantaneousRequest.handleAsOf(request, new DateTime()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/IssueNewTokenActivity.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.friendlyserve.Response 5 | import com.clouway.oauth2.client.Client 6 | import com.clouway.oauth2.common.DateTime 7 | import com.clouway.oauth2.token.GrantType 8 | import com.clouway.oauth2.token.IdTokenFactory 9 | import com.clouway.oauth2.token.Identity 10 | import com.clouway.oauth2.token.TokenRequest 11 | import com.clouway.oauth2.token.Tokens 12 | 13 | /** 14 | * IssueNewTokenActivity is representing the activity which is performed for issuing of new token. 15 | * 16 | * @author Miroslav Genov (miroslav.genov@clouway.com) 17 | */ 18 | class IssueNewTokenActivity( 19 | private val tokens: Tokens, 20 | private val idTokenFactory: IdTokenFactory, 21 | ) : AuthorizedIdentityActivity { 22 | override fun execute( 23 | client: Client, 24 | identity: Identity, 25 | scopes: Set, 26 | request: Request, 27 | instant: DateTime, 28 | params: Map, 29 | ): Response { 30 | val response = 31 | tokens.issueToken( 32 | TokenRequest( 33 | grantType = GrantType.AUTHORIZATION_CODE, 34 | client = client, 35 | identity = identity, 36 | scopes = scopes, 37 | `when` = instant, 38 | params = params, 39 | ), 40 | ) 41 | 42 | if (!response.isSuccessful) { 43 | return OAuthError.invalidRequest("Token cannot be issued.") 44 | } 45 | 46 | val accessToken = response.accessToken 47 | val possibleIdToken = 48 | idTokenFactory.create( 49 | request.header("Host"), 50 | client.id, 51 | identity, 52 | accessToken.ttlSeconds(instant), 53 | instant, 54 | ) 55 | 56 | if (possibleIdToken.isPresent) { 57 | return BearerTokenResponse( 58 | accessToken.value, 59 | accessToken.ttlSeconds(instant), 60 | accessToken.scopes, 61 | response.refreshToken, 62 | possibleIdToken.get(), 63 | ) 64 | } 65 | 66 | return BearerTokenResponse( 67 | accessToken.value, 68 | accessToken.ttlSeconds(instant), 69 | accessToken.scopes, 70 | response.refreshToken, 71 | ) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/OAuth2ApiSupport.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | import java.io.IOException; 6 | 7 | /** 8 | * @author Mihail Lesikov (mihail.lesikov@clouway.com) 9 | */ 10 | public interface OAuth2ApiSupport { 11 | 12 | void serve(HttpServletRequest req, HttpServletResponse resp) throws IOException; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/OAuth2Servlet.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import javax.servlet.ServletConfig; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.http.HttpServlet; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | 10 | /** 11 | * @author Miroslav Genov (miroslav.genov@clouway.com) 12 | */ 13 | public abstract class OAuth2Servlet extends HttpServlet { 14 | private OAuth2ApiSupport apiSupport; 15 | 16 | @Override 17 | public void init(ServletConfig servletConfig) throws ServletException { 18 | super.init(servletConfig); 19 | apiSupport = new OAuth2ApiSupportFactory().create(config()); 20 | } 21 | 22 | @Override 23 | protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 24 | super.service(req, resp); 25 | 26 | } 27 | 28 | @Override 29 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 30 | doPost(req, resp); 31 | } 32 | 33 | @Override 34 | protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { 35 | apiSupport.serve(req, resp); 36 | } 37 | 38 | protected abstract OAuth2Config config(); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/OAuthError.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Response; 4 | import com.clouway.friendlyserve.RsJson; 5 | import com.clouway.friendlyserve.RsWithStatus; 6 | import com.clouway.friendlyserve.RsWrap; 7 | import com.google.gson.JsonObject; 8 | 9 | import java.net.HttpURLConnection; 10 | 11 | /** 12 | * @author Miroslav Genov (miroslav.genov@clouway.com) 13 | */ 14 | public final class OAuthError extends RsWrap { 15 | 16 | public static OAuthError invalidRequest() { 17 | return new OAuthError("invalid_request", HttpURLConnection.HTTP_BAD_REQUEST); 18 | } 19 | 20 | public static OAuthError invalidRequest(String description) { 21 | return new OAuthError(HttpURLConnection.HTTP_BAD_REQUEST, "invalid_request", description); 22 | } 23 | 24 | public static OAuthError invalidClient() { 25 | return new OAuthError("invalid_client", HttpURLConnection.HTTP_BAD_REQUEST); 26 | } 27 | 28 | public static OAuthError invalidClient(String description) { 29 | return new OAuthError(HttpURLConnection.HTTP_BAD_REQUEST,"invalid_client",description); 30 | } 31 | 32 | public static OAuthError unauthorizedClient() { 33 | return new OAuthError("unauthorized_client", HttpURLConnection.HTTP_BAD_REQUEST); 34 | } 35 | 36 | public static OAuthError unauthorizedClient(String description) { 37 | return new OAuthError(HttpURLConnection.HTTP_BAD_REQUEST, "unauthorized_client", description); 38 | } 39 | 40 | public static OAuthError invalidGrant() { 41 | return new OAuthError("invalid_grant", HttpURLConnection.HTTP_BAD_REQUEST); 42 | } 43 | 44 | public static OAuthError invalidGrant(String description) { 45 | return new OAuthError(HttpURLConnection.HTTP_BAD_REQUEST, "invalid_grant", description); 46 | } 47 | 48 | public static OAuthError internalError() { 49 | return new OAuthError(HttpURLConnection.HTTP_BAD_GATEWAY, "internal_error", "Internal server error was occurred"); 50 | } 51 | 52 | public static OAuthError unknownClient() { 53 | return new OAuthError(HttpURLConnection.HTTP_BAD_REQUEST, "invalid_client", "Unknown Client"); 54 | } 55 | 56 | public static OAuthError invalidToken() { 57 | return new OAuthError(HttpURLConnection.HTTP_UNAUTHORIZED, "invalid_token", "Invalid Credentials"); 58 | } 59 | 60 | public static OAuthError invalidToken(String description) { 61 | return new OAuthError(HttpURLConnection.HTTP_UNAUTHORIZED, "invalid_token", description); 62 | } 63 | 64 | private OAuthError(String errorName, int responseCode) { 65 | super(new RsWithStatus(responseCode, createJsonResponse(errorName))); 66 | } 67 | 68 | private OAuthError(int responseCode, String erroName, String errorDescription) { 69 | super(new RsWithStatus(responseCode, createJsonResponse(erroName, errorDescription))); 70 | } 71 | 72 | private static Response createJsonResponse(String errorName) { 73 | JsonObject o = new JsonObject(); 74 | o.addProperty("error", errorName); 75 | return new RsJson(o); 76 | } 77 | 78 | private static Response createJsonResponse(String errorName, String errorDescription) { 79 | JsonObject o = new JsonObject(); 80 | o.addProperty("error", errorName); 81 | o.addProperty("error_description", errorDescription); 82 | return new RsJson(o); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/PublicCertsController.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.clouway.friendlyserve.Response; 5 | import com.clouway.friendlyserve.RsJson; 6 | import com.clouway.friendlyserve.Take; 7 | import com.clouway.oauth2.keystore.IdentityKeyPair; 8 | import com.clouway.oauth2.jws.Pem; 9 | import com.clouway.oauth2.jws.Pem.Block; 10 | import com.clouway.oauth2.keystore.KeyStore; 11 | import com.google.gson.JsonObject; 12 | 13 | import java.io.IOException; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | /** 18 | * @author Ianislav Nachev 19 | */ 20 | public class PublicCertsController implements Take { 21 | private final KeyStore keyStore; 22 | 23 | public PublicCertsController(KeyStore keyStore) { 24 | this.keyStore = keyStore; 25 | } 26 | 27 | @Override 28 | public Response ack(Request request) throws IOException { 29 | List keyMap = keyStore.getKeys(); 30 | 31 | JsonObject o = new JsonObject(); 32 | if (keyMap.isEmpty()) { 33 | return new RsJson(o); 34 | } 35 | 36 | Pem pem = new Pem(); 37 | 38 | for (IdentityKeyPair each : keyMap) { 39 | String certificateAsText = pem.format( 40 | new Block("CERTIFICATE", Collections.emptyMap(), each.publicKey.getEncoded()) 41 | ); 42 | o.addProperty(each.keyId, certificateAsText); 43 | } 44 | 45 | return new RsJson(o); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/ResourceOwnerIdentityFinder.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.clouway.oauth2.common.DateTime; 5 | import com.google.common.base.Optional; 6 | 7 | /** 8 | * ResourceOwnerIdentityFinder is a finder which is used during the authorization of the request. 9 | *

10 | *

11 | * In most cases implementations of this class should ask the session store for retrieving of the identity which is 12 | * assicated with the request. 13 | *

14 | * 15 | * @author Miroslav Genov (miroslav.genov@clouway.com) 16 | */ 17 | public interface ResourceOwnerIdentityFinder { 18 | 19 | /** 20 | * Finds Identity of the caller. 21 | *

22 | * In most cases the request will contain a Cookie value which will be used 23 | * as lookup key for the session of the Owner which contains and the requested identity. 24 | * 25 | * @param request the request from which identity will be retrieved 26 | * @param instantTime the time on which request was executed 27 | * @return an optional value with the identity id if it's associated with the provided request or absent value if not 28 | */ 29 | Optional find(Request request, DateTime instantTime); 30 | } 31 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/ResponseWriter.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | 4 | /** 5 | * @author Miroslav Genov (miroslav.genov@clouway.com) 6 | */ 7 | interface ResponseWriter { 8 | void badRequest(); 9 | 10 | void writeResponse(Object response); 11 | } 12 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/RevokeTokenController.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.clouway.friendlyserve.Response; 5 | import com.clouway.friendlyserve.RsEmpty; 6 | import com.clouway.oauth2.client.Client; 7 | import com.clouway.oauth2.client.ClientFinder; 8 | import com.clouway.oauth2.client.ClientCredentials; 9 | import com.clouway.oauth2.common.DateTime; 10 | import com.clouway.oauth2.token.BearerToken; 11 | import com.clouway.oauth2.token.Tokens; 12 | import com.google.common.base.Optional; 13 | 14 | /** 15 | * @author Miroslav Genov (miroslav.genov@clouway.com) 16 | */ 17 | class RevokeTokenController implements ClientRequest { 18 | 19 | private final ClientFinder clientFinder; 20 | private final Tokens tokens; 21 | 22 | RevokeTokenController(ClientFinder clientFinder, Tokens tokens) { 23 | this.clientFinder = clientFinder; 24 | this.tokens = tokens; 25 | } 26 | 27 | @Override 28 | public Response handleAsOf(Request request, ClientCredentials credentials, DateTime instant) { 29 | String token = request.param("token"); 30 | 31 | Optional possibleToken = tokens.findTokenAvailableAt(token, instant); 32 | if (!possibleToken.isPresent()) { 33 | return OAuthError.invalidRequest("Token was not found."); 34 | } 35 | 36 | Optional possibleClient = clientFinder.findClient(possibleToken.get().clientId); 37 | if (!possibleClient.isPresent()) { 38 | return OAuthError.unauthorizedClient("A client with id:" + possibleToken.get().clientId + " was either not found or is not authorized."); 39 | } 40 | 41 | Client client = possibleClient.get(); 42 | 43 | if (!client.credentialsMatch(credentials)) { 44 | return OAuthError.unauthorizedClient("Client credentials mismatch."); 45 | } 46 | 47 | tokens.revokeToken(token); 48 | 49 | return new RsEmpty(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/UserInfoController.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.friendlyserve.Response 5 | import com.clouway.friendlyserve.RsJson 6 | import com.clouway.oauth2.common.DateTime 7 | import com.clouway.oauth2.token.FindIdentityRequest 8 | import com.clouway.oauth2.token.FindIdentityResult 9 | import com.clouway.oauth2.token.IdentityFinder 10 | import com.clouway.oauth2.token.Tokens 11 | import com.google.gson.JsonObject 12 | 13 | /** 14 | * @author Miroslav Genov (miroslav.genov@clouway.com) 15 | */ 16 | internal class UserInfoController( 17 | private val identityFinder: IdentityFinder, 18 | private val tokens: Tokens, 19 | ) : InstantaneousRequest { 20 | override fun handleAsOf( 21 | request: Request, 22 | instantTime: DateTime, 23 | ): Response { 24 | val accessToken = request.param("access_token") 25 | 26 | val possibleTokenResponse = tokens.findTokenAvailableAt(accessToken, instantTime) 27 | 28 | if (!possibleTokenResponse.isPresent) { 29 | return OAuthError.invalidToken("Access token was not found.") 30 | } 31 | 32 | val token = possibleTokenResponse.get() 33 | 34 | val findReq = 35 | FindIdentityRequest( 36 | token.identityId, 37 | token.grantType, 38 | instantTime, 39 | token.params, 40 | token.clientId, 41 | ) 42 | 43 | val o = JsonObject() 44 | var claims: Map = mutableMapOf() 45 | when (val res = identityFinder.findIdentity(findReq)) { 46 | is FindIdentityResult.User -> { 47 | val identity = res.identity 48 | o.addProperty("id", identity.id) 49 | o.addProperty("name", identity.name) 50 | o.addProperty("email", identity.email) 51 | o.addProperty("given_name", identity.givenName) 52 | o.addProperty("family_name", identity.familyName) 53 | 54 | claims = identity.claims 55 | } 56 | is FindIdentityResult.ServiceAccountClient -> { 57 | val serviceAccount = res.serviceAccount 58 | o.addProperty("id", serviceAccount.clientId) 59 | o.addProperty("name", serviceAccount.name) 60 | o.addProperty("email", serviceAccount.clientEmail) 61 | o.addProperty("given_name", "") 62 | o.addProperty("family_name", "") 63 | 64 | claims = serviceAccount.claims 65 | } 66 | is FindIdentityResult.NotFound -> { 67 | return OAuthError.invalidGrant("Identity was not found.") 68 | } 69 | is FindIdentityResult.Error -> { 70 | return OAuthError.internalError() 71 | } 72 | } 73 | 74 | for (key in claims.keys) { 75 | val value = claims[key] 76 | 77 | if (value is String) { 78 | o.addProperty(key, value) 79 | } 80 | if (value is Number) { 81 | o.addProperty(key, value) 82 | } 83 | 84 | if (value is Boolean) { 85 | o.addProperty(key, value) 86 | } 87 | } 88 | 89 | return RsJson(o) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/authorization/Authorization.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.authorization 2 | 3 | import com.clouway.oauth2.codechallenge.CodeChallenge 4 | 5 | /** 6 | * @author Ivan Stefanov @clouway.com> 7 | */ 8 | data class Authorization( 9 | @JvmField val responseType: String = "authorization_code", 10 | @JvmField val clientId: String = "", 11 | @JvmField val identityId: String = "", 12 | @JvmField val code: String, 13 | @JvmField val scopes: Set = setOf(), 14 | @JvmField val redirectUrls: Set = setOf(), 15 | @JvmField val codeChallenge: CodeChallenge, 16 | @JvmField val params: Map 17 | ) -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/authorization/AuthorizationRequest.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.authorization 2 | 3 | import com.clouway.oauth2.codechallenge.CodeChallenge 4 | import java.time.LocalDateTime 5 | 6 | /** 7 | * @author Vasil Mitov @clouway.com> 8 | */ 9 | data class AuthorizationRequest( 10 | @JvmField val clientId: String, 11 | @JvmField val identityId: String, 12 | @JvmField val responseType: String, 13 | @JvmField val scopes: Set, 14 | @JvmField val codeChallenge: CodeChallenge, 15 | @JvmField val params: Map, 16 | @JvmField val time: LocalDateTime 17 | ) -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/authorization/AuthorizationResponse.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.authorization; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.net.URLEncoder; 5 | 6 | /** 7 | * @author Ivan Stefanov 8 | */ 9 | public class AuthorizationResponse { 10 | private final String code; 11 | private final String redirectURI; 12 | 13 | public AuthorizationResponse(String code, String redirectURI) { 14 | this.code = code; 15 | this.redirectURI = redirectURI; 16 | } 17 | 18 | public String buildURI() { 19 | try { 20 | return redirectURI + "?code=" + URLEncoder.encode(code, "UTF-8"); 21 | } catch (UnsupportedEncodingException e) { 22 | return redirectURI + "?code=" + code; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/authorization/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "authorization", 7 | srcs = glob([ 8 | "*.kt", 9 | "*.java", 10 | ]), 11 | deps = [ 12 | "//oauth2-server/src/main/java/com/clouway/oauth2/client", 13 | "//oauth2-server/src/main/java/com/clouway/oauth2/codechallenge", 14 | "//oauth2-server/src/main/java/com/clouway/oauth2/common", 15 | "@maven//:com_google_guava_guava", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/authorization/ClientAuthorizer.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.authorization 2 | 3 | import com.clouway.oauth2.client.Client 4 | import com.clouway.oauth2.common.DateTime 5 | 6 | /** 7 | * ClientAuthorizationRepository is a repository which is keeping records for the authroziations 8 | * that are performed for the client applications. 9 | * 10 | * @author Ivan Stefanov @clouway.com> 11 | */ 12 | interface ClientAuthorizer { 13 | 14 | /** 15 | * Authorizes client by issuing a new authorization code to be used 16 | * latter in the exchange for a token. 17 | */ 18 | fun authorizeClient(req: AuthorizationRequest): ClientAuthorizationResult 19 | 20 | /** 21 | * Finds authorization that is associated with the provided authCode. 22 | * 23 | * @param client the client for which authorization was issued 24 | * @param authCode the code of the authorization 25 | * @param instant the time on which check is performed 26 | * @return the authorization or absent value 27 | */ 28 | fun findAuthorization(clientId: String, authCode: String, instant: DateTime): FindAuthorizationResult 29 | 30 | 31 | } 32 | 33 | sealed class ClientAuthorizationResult { 34 | data class Success(val client: Client, val authCode: String) : ClientAuthorizationResult() 35 | 36 | data class AccessDenied(val reason: String) : ClientAuthorizationResult() 37 | 38 | class ClientNotFound() : ClientAuthorizationResult() 39 | 40 | data class Error(val message: String?) : ClientAuthorizationResult() 41 | } 42 | 43 | sealed class FindAuthorizationResult { 44 | 45 | data class Success(val authorization: Authorization, val client: Client) : FindAuthorizationResult() 46 | 47 | class NotFound() : FindAuthorizationResult() 48 | 49 | class ClientNotFound() : FindAuthorizationResult() 50 | 51 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/client/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "client", 7 | srcs = glob([ 8 | "*.kt", 9 | "*.java", 10 | ]), 11 | deps = [ 12 | "//oauth2-server/src/main/java/com/clouway/oauth2/codechallenge", 13 | "//oauth2-server/src/main/java/com/clouway/oauth2/jws", 14 | "//oauth2-server/src/main/java/com/clouway/oauth2/jwt", 15 | "@maven//:com_google_guava_guava", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/client/Client.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.client; 2 | 3 | import com.google.common.base.MoreObjects; 4 | import com.google.common.base.Optional; 5 | import com.google.common.collect.Iterables; 6 | 7 | import java.util.Objects; 8 | import java.util.Set; 9 | 10 | import static com.google.common.base.Strings.isNullOrEmpty; 11 | 12 | /** 13 | * @author Ivan Stefanov 14 | */ 15 | public class Client { 16 | public final String id; 17 | public final String secret; 18 | public final String description; 19 | public final Set redirectURLs; 20 | public final boolean publicOne; 21 | 22 | public Client(String id, String secret, String description, Set redirectURLs, boolean publicOne) { 23 | this.id = id; 24 | this.secret = secret; 25 | this.description = description; 26 | this.redirectURLs = redirectURLs; 27 | this.publicOne = publicOne; 28 | } 29 | 30 | public Optional determineRedirectUrl(String requestedUrl) { 31 | if (isNullOrEmpty(requestedUrl)) { 32 | return Optional.fromNullable(Iterables.getFirst(redirectURLs, "http://client.was.not.configured.properly.com")); 33 | } 34 | 35 | if (!redirectURLs.contains(requestedUrl)) { 36 | return Optional.absent(); 37 | } 38 | 39 | return Optional.of(requestedUrl); 40 | } 41 | 42 | public boolean credentialsMatch(ClientCredentials credentials) { 43 | // Just compare the ID as public clients are not exposing secrets to 44 | // the apps (android, osx and etc). 45 | // Described in: https://tools.ietf.org/html/rfc6749#section-2.3 46 | if (publicOne) { 47 | return id.equalsIgnoreCase(credentials.clientId()); 48 | } 49 | 50 | return secret.equalsIgnoreCase(credentials.clientSecret()); 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (o == null || getClass() != o.getClass()) return false; 57 | Client client = (Client) o; 58 | return Objects.equals(id, client.id) && 59 | Objects.equals(secret, client.secret) && 60 | Objects.equals(description, client.description) && 61 | Objects.equals(redirectURLs, client.redirectURLs) && 62 | Objects.equals(publicOne, client.publicOne); 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Objects.hash(id, secret, description, redirectURLs); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return MoreObjects.toStringHelper(this) 73 | .add("id", id) 74 | .add("secret", secret) 75 | .add("description", description) 76 | .add("redirectURLs", redirectURLs) 77 | .add("publicOne", publicOne) 78 | .toString(); 79 | } 80 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/client/ClientCredentials.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.client; 2 | 3 | import com.google.common.base.MoreObjects; 4 | import com.google.common.base.Objects; 5 | 6 | /** 7 | * ClientCredentials is representing pair of id and secret of the client app. 8 | * 9 | * @author Miroslav Genov (miroslav.genov@clouway.com) 10 | */ 11 | public class ClientCredentials { 12 | private final String clientId; 13 | private final String clientSecret; 14 | 15 | public ClientCredentials(String clientId, String clientSecret) { 16 | this.clientId = clientId; 17 | this.clientSecret = clientSecret; 18 | } 19 | 20 | public String clientId() { 21 | return clientId; 22 | } 23 | 24 | public String clientSecret() { 25 | return clientSecret; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (o == null || getClass() != o.getClass()) return false; 32 | ClientCredentials that = (ClientCredentials) o; 33 | return Objects.equal(clientId, that.clientId) && 34 | Objects.equal(clientSecret, that.clientSecret); 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return Objects.hashCode(clientId, clientSecret); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return MoreObjects.toStringHelper(this) 45 | .add("clientId", clientId) 46 | .add("clientSecret", clientSecret) 47 | .toString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/client/ClientFinder.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.client 2 | 3 | import com.google.common.base.Optional 4 | 5 | /** 6 | * ClientAuthorizer is an authorized used to authorize passed 7 | * @author Mihail Lesikov (mlesikov@gmail.com) 8 | */ 9 | interface ClientFinder { 10 | fun findClient(clientId: String): Optional 11 | } 12 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/client/ClientRegistrationRequest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.client; 2 | 3 | import java.util.Set; 4 | 5 | public class ClientRegistrationRequest { 6 | public final String secret; 7 | public final String description; 8 | public final Set redirectURLs; 9 | 10 | public ClientRegistrationRequest(String secret, String description, Set redirectURLs) { 11 | this.secret = secret; 12 | this.description = description; 13 | this.redirectURLs = redirectURLs; 14 | } 15 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/client/JwtKeyStore.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.client; 2 | 3 | import com.clouway.oauth2.jws.Pem; 4 | import com.clouway.oauth2.jwt.Jwt; 5 | import com.google.common.base.Optional; 6 | 7 | /** 8 | * JwtKeyStore is a KeyStore which is responsible for retrieving of Key blocks for verifying 9 | * the JWT authorization. 10 | *

11 | * The implementations of this class should retrieve from secured store private keys which will 12 | * be used for verification of the received JWT tokens. 13 | * 14 | * @author Miroslav Genov (miroslav.genov@clouway.com) 15 | */ 16 | public interface JwtKeyStore { 17 | 18 | /** 19 | * Finds associated KEY for the provided claim set. 20 | * 21 | * @param header the jwt header that specifies the type of the algorithm 22 | * @param claimSet the claim set of which service account is requested 23 | * @return the key for that service account 24 | */ 25 | Optional findKey(Jwt.Header header, Jwt.ClaimSet claimSet); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/client/RegistrationRequest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.client; 2 | 3 | /** 4 | * @author Ivan Stefanov 5 | */ 6 | public class RegistrationRequest { 7 | public final String name; 8 | public final String url; 9 | public final String description; 10 | public final String redirectURI; 11 | 12 | public RegistrationRequest(String name, String url, String description, String redirectURI) { 13 | this.name = name; 14 | this.url = url; 15 | this.description = description; 16 | this.redirectURI = redirectURI; 17 | } 18 | 19 | @Override 20 | public boolean equals(Object o) { 21 | if (this == o) return true; 22 | if (o == null || getClass() != o.getClass()) return false; 23 | 24 | RegistrationRequest that = (RegistrationRequest) o; 25 | 26 | if (description != null ? !description.equals(that.description) : that.description != null) return false; 27 | if (name != null ? !name.equals(that.name) : that.name != null) return false; 28 | if (redirectURI != null ? !redirectURI.equals(that.redirectURI) : that.redirectURI != null) return false; 29 | if (url != null ? !url.equals(that.url) : that.url != null) return false; 30 | 31 | return true; 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | int result = name != null ? name.hashCode() : 0; 37 | result = 31 * result + (url != null ? url.hashCode() : 0); 38 | result = 31 * result + (description != null ? description.hashCode() : 0); 39 | result = 31 * result + (redirectURI != null ? redirectURI.hashCode() : 0); 40 | return result; 41 | } 42 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/client/RegistrationResponse.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.client; 2 | 3 | /** 4 | * @author Ivan Stefanov 5 | */ 6 | public class RegistrationResponse { 7 | public final String clientId; 8 | public final String clientSecret; 9 | 10 | public RegistrationResponse(String clientId, String clientSecret) { 11 | this.clientId = clientId; 12 | this.clientSecret = clientSecret; 13 | } 14 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/codechallenge/AuthorizationCodeVerifier.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.codechallenge 2 | 3 | import java.nio.charset.StandardCharsets 4 | import java.security.MessageDigest 5 | import java.security.NoSuchAlgorithmException 6 | import java.util.Base64 7 | 8 | /** 9 | * @author Vasil Mitov @clouway.com> 10 | */ 11 | class AuthorizationCodeVerifier : CodeVerifier { 12 | override fun verify(codeChallenge: CodeChallenge, providedCodeVerifier: String): Boolean { 13 | try { 14 | //no code verifier was provided and no code challenge was saved so this is a normal OAuth2 code flow 15 | if (providedCodeVerifier.isEmpty() && !codeChallenge.isProvided) { 16 | return true 17 | } 18 | //there is a saved code challenge but provided providedCodeVerifier is empty. 19 | if (providedCodeVerifier.isEmpty() && codeChallenge.isProvided) { 20 | return false 21 | } 22 | //providedCodeVerifier was provided but no codeChallenge was saved. 23 | if (providedCodeVerifier.isNotEmpty() && !codeChallenge.isProvided) { 24 | return false 25 | } 26 | 27 | if (codeChallenge.method == "plain") { 28 | return codeChallenge.transformedCodeChallenge == providedCodeVerifier 29 | } 30 | 31 | if (codeChallenge.method == "S256") { 32 | val hashed = MessageDigest.getInstance("SHA-256").digest( 33 | providedCodeVerifier.toByteArray( 34 | StandardCharsets.UTF_8 35 | ) 36 | ) 37 | 38 | val codeChallengeValue = Base64.getUrlEncoder().withoutPadding().encodeToString(hashed) 39 | 40 | return codeChallengeValue == codeChallenge.transformedCodeChallenge 41 | } 42 | return false 43 | } catch (e: NoSuchAlgorithmException) { 44 | return false 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/codechallenge/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "codechallenge", 7 | srcs = glob([ 8 | "*.kt", 9 | "*.java", 10 | ]), 11 | deps = [ 12 | "@maven//:com_google_guava_guava", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/codechallenge/CodeChallenge.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.codechallenge; 2 | 3 | import com.google.common.base.Objects; 4 | 5 | /** 6 | * @author Vasil Mitov 7 | */ 8 | public class CodeChallenge { 9 | public String transformedCodeChallenge = ""; 10 | public String method = ""; 11 | 12 | public CodeChallenge(String transformedCodeChallenge, String method) { 13 | this.transformedCodeChallenge = transformedCodeChallenge; 14 | this.method = method; 15 | } 16 | 17 | public boolean isProvided() { 18 | return !this.transformedCodeChallenge.isEmpty() && !this.method.isEmpty(); 19 | } 20 | 21 | @Override 22 | public boolean equals(Object o) { 23 | if (this == o) return true; 24 | if (o == null || getClass() != o.getClass()) return false; 25 | CodeChallenge that = (CodeChallenge) o; 26 | return Objects.equal(transformedCodeChallenge, that.transformedCodeChallenge) && 27 | Objects.equal(method, that.method); 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | return Objects.hashCode(transformedCodeChallenge, method); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/codechallenge/CodeVerifier.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.codechallenge; 2 | 3 | /** 4 | * @author Vasil Mitov 5 | */ 6 | public interface CodeVerifier { 7 | 8 | /** 9 | * Verify the given codeChallenge with the codeVerifier 10 | * 11 | * @param codeChallenge the code challenge provided upon authorization 12 | * @param codeVerifier the code verifier provided on a request for new token 13 | * @return true if the two values match false if they don't 14 | */ 15 | boolean verify(CodeChallenge codeChallenge, String codeVerifier); 16 | } 17 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/common/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "common", 7 | srcs = glob(["*.kt", "*.java"]), 8 | deps = [ 9 | "@maven//:com_google_guava_guava", 10 | ] 11 | ) -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/common/DateTime.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.common; 2 | 3 | import java.io.Serializable; 4 | import java.time.LocalDateTime; 5 | import java.time.ZoneOffset; 6 | import java.util.Date; 7 | import java.util.Objects; 8 | 9 | /** 10 | * @author Miroslav Genov (miroslav.genov@clouway.com) 11 | */ 12 | public final class DateTime implements Serializable { 13 | private final Date time; 14 | 15 | public DateTime(Date time) { 16 | this.time = time; 17 | } 18 | 19 | public DateTime(Long timeAsMillis) { 20 | this.time = new Date(timeAsMillis); 21 | } 22 | 23 | public DateTime() { 24 | this.time = new Date(); 25 | } 26 | 27 | public boolean after(DateTime date) { 28 | return time.after(date.asDate()); 29 | } 30 | 31 | public boolean before(DateTime date) { 32 | return time.before(date.asDate()); 33 | } 34 | 35 | public DateTime plusSeconds(long seconds) { 36 | return new DateTime(new Date(time.getTime() + 1000 * seconds)); 37 | } 38 | 39 | public DateTime minusSeconds(long seconds) { 40 | return new DateTime(new Date(time.getTime() - 1000 * seconds)); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return "DateTime{" + time + '}'; 46 | } 47 | 48 | public Date asDate() { 49 | return new Date(time.getTime()); 50 | } 51 | 52 | public Long timestamp() { 53 | return time.getTime(); 54 | } 55 | 56 | public LocalDateTime toLocalDateTime() { 57 | return time.toInstant().atZone(ZoneOffset.UTC).toLocalDateTime(); 58 | } 59 | 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) return true; 63 | if (o == null || getClass() != o.getClass()) return false; 64 | DateTime dateTime = (DateTime) o; 65 | return Objects.equals(time, dateTime.time); 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | return Objects.hash(time); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/common/Duration.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.common; 2 | 3 | /** 4 | * @author Ivan Stefanov 5 | */ 6 | public class Duration { 7 | public Long seconds; 8 | 9 | public Duration() { 10 | } 11 | 12 | public Duration(Long seconds) { 13 | this.seconds = seconds; 14 | } 15 | 16 | public static Duration minutes(Integer minutes) { 17 | return new Duration(minutes * 60L); 18 | } 19 | 20 | public static Duration hours(Integer hours) { 21 | return new Duration(hours * 60 * 60L); 22 | } 23 | 24 | public static Duration seconds(Long seconds) { 25 | return new Duration(seconds); 26 | } 27 | 28 | public Long asMills() { 29 | return seconds * 1000; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) return true; 35 | if (!(o instanceof Duration)) return false; 36 | 37 | Duration duration = (Duration) o; 38 | 39 | if (seconds != null ? !seconds.equals(duration.seconds) : duration.seconds != null) return false; 40 | 41 | return true; 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return seconds != null ? seconds.hashCode() : 0; 47 | } 48 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/jws/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "jws", 7 | srcs = glob(["*.kt", "*.java"]), 8 | deps = [ 9 | "//oauth2-server/src/main/java/com/clouway/oauth2/jwt", 10 | "@maven//:com_google_guava_guava", 11 | ] 12 | ) -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/jws/Pem.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.jws; 2 | 3 | import com.google.common.base.MoreObjects; 4 | import com.google.common.base.Splitter; 5 | import com.google.common.collect.ImmutableMap; 6 | import com.google.common.io.BaseEncoding; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.io.PrintWriter; 14 | import java.util.Arrays; 15 | import java.util.Map; 16 | import java.util.Objects; 17 | import java.util.regex.Matcher; 18 | import java.util.regex.Pattern; 19 | 20 | import static com.google.common.base.Preconditions.checkArgument; 21 | 22 | /** 23 | * @author Miroslav Genov (miroslav.genov@clouway.com) 24 | */ 25 | public final class Pem { 26 | private static final Pattern BEGIN_PATTERN = Pattern.compile("-----BEGIN ([A-Z ]+)-----"); 27 | private static final Pattern END_PATTERN = Pattern.compile("-----END ([A-Z ]+)-----"); 28 | 29 | public static class Block { 30 | private final String type; 31 | private final Map headers; 32 | private final byte[] content; 33 | 34 | public Block(String type, Map headers, byte[] content) { 35 | this.type = type; 36 | this.headers = headers; 37 | this.content = content; 38 | } 39 | 40 | public byte[] getBytes() { 41 | return content; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | Block block = (Block) o; 49 | return Objects.equals(type, block.type) && 50 | Objects.equals(headers, block.headers) && 51 | Arrays.equals(content, block.content); 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return Objects.hash(type, headers, Arrays.hashCode(content)); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return MoreObjects.toStringHelper(this) 62 | .add("type", type) 63 | .add("headers", headers) 64 | .add("content", content) 65 | .toString(); 66 | } 67 | } 68 | 69 | public Block parse(InputStream stream) throws IOException { 70 | BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); 71 | 72 | String title = null; 73 | StringBuilder keyBuilder = null; 74 | while (true) { 75 | String line = reader.readLine(); 76 | if (line == null) { 77 | checkArgument(title == null, "missing end tag (%s)", title); 78 | return null; 79 | } 80 | if (keyBuilder == null) { 81 | Matcher m = BEGIN_PATTERN.matcher(line); 82 | if (m.matches()) { 83 | String curTitle = m.group(1); 84 | 85 | keyBuilder = new StringBuilder(); 86 | title = curTitle; 87 | } 88 | } else { 89 | Matcher m = END_PATTERN.matcher(line); 90 | if (m.matches()) { 91 | String endTitle = m.group(1); 92 | checkArgument(endTitle.equals(title), 93 | "end tag (%s) doesn't match begin tag (%s)", endTitle, title); 94 | break; 95 | } 96 | 97 | keyBuilder.append(line); 98 | } 99 | } 100 | 101 | byte[] content = BaseEncoding.base64().decode(keyBuilder.toString()); 102 | 103 | return new Block(title, ImmutableMap.of(), content); 104 | } 105 | 106 | public String format(Block block) { 107 | String keyAsHex = BaseEncoding.base64().encode(block.content); 108 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 109 | PrintWriter writer = new PrintWriter(bout); 110 | writer.println(String.format("-----BEGIN %s-----", block.type)); 111 | for (String line : Splitter.fixedLength(64).split(keyAsHex)) { 112 | writer.println(line); 113 | } 114 | writer.println(String.format("-----END %s-----", block.type)); 115 | writer.flush(); 116 | return bout.toString(); 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/jws/RsaJwsSignature.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.jws; 2 | 3 | import java.security.KeyFactory; 4 | import java.security.PrivateKey; 5 | import java.security.Signature; 6 | import java.security.spec.PKCS8EncodedKeySpec; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * RsaJwsSignature is an implementation of {@link com.clouway.oauth2.jws.Signature} that uses 11 | * RSA algorithm. 12 | * 13 | * @author Miroslav Genov (miroslav.genov@clouway.com) 14 | */ 15 | public class RsaJwsSignature implements com.clouway.oauth2.jws.Signature { 16 | private byte[] signature; 17 | 18 | 19 | public RsaJwsSignature(byte[] signature) { 20 | this.signature = signature; 21 | } 22 | 23 | public boolean verifyWithPrivateKey(byte[] content, Pem.Block privateKey) { 24 | try { 25 | 26 | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey.getBytes()); 27 | KeyFactory kf = KeyFactory.getInstance("RSA"); 28 | 29 | PrivateKey privKey = kf.generatePrivate(keySpec); 30 | 31 | Signature sig = Signature.getInstance("SHA256withRSA"); 32 | sig.initSign(privKey); 33 | 34 | sig.update(content); 35 | 36 | byte[] checkSign = sig.sign(); 37 | 38 | return Arrays.equals(signature, checkSign); 39 | 40 | } catch (Exception e) { 41 | return false; 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/jws/Signature.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.jws; 2 | 3 | /** 4 | * Signature is representing a single JWS signature that is applied over received messages. 5 | *

6 | * 7 | * @author Miroslav Genov (miroslav.genov@clouway.com) 8 | */ 9 | public interface Signature { 10 | 11 | /** 12 | * Verify is verifying Signature using the provided privateKey as PEM file. 13 | *

14 | * 15 | * @param content the content to be verified 16 | * @param privateKey the private key used for verifying 17 | * @return true if signature is 18 | */ 19 | boolean verifyWithPrivateKey(byte[] content, Pem.Block privateKey); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/jws/SignatureFactory.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.jws; 2 | 3 | import com.clouway.oauth2.jwt.Jwt.Header; 4 | import com.google.common.base.Optional; 5 | 6 | /** 7 | * SignatureFactory is a factory class which is creating a signature by providing it's signature value and a Header that 8 | * comes from the request to be able to determine the type of the Signature. 9 | *

10 | * Currently only RSA Signature is supported. 11 | * 12 | * @author Miroslav Genov (miroslav.genov@clouway.com) 13 | */ 14 | public interface SignatureFactory { 15 | 16 | /** 17 | * Finds signature that should be applied for the provided header. 18 | * 19 | * @param signatureValue as byte array 20 | * @param header the header to be used 21 | * @return the 22 | */ 23 | Optional createSignature(byte[] signatureValue, Header header); 24 | } 25 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/jwt/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "jwt", 7 | srcs = glob(["*.kt", "*.java"]), 8 | deps = [ 9 | "@maven//:com_google_guava_guava", 10 | ] 11 | ) -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/keystore/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "keystore", 7 | srcs = glob(["*.kt", "*.java"]), 8 | ) -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/keystore/IdentityKeyPair.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.keystore; 2 | 3 | import java.security.PrivateKey; 4 | import java.security.PublicKey; 5 | 6 | /** 7 | * IdentityKeyPair is a key pair which is used to represent identity, public and private keys that are used for signing 8 | * of id_token. The identity in the key is used as marker to mark the key that was used for signing and this ID is encoded 9 | * in the header of the signature to provide a clean way for safe verification of the signature. 10 | * 11 | * @author Miroslav Genov (miroslav.genov@clouway.com) 12 | */ 13 | public final class IdentityKeyPair { 14 | public final String keyId; 15 | public final PrivateKey privateKey; 16 | public final PublicKey publicKey; 17 | 18 | public IdentityKeyPair(String keyId, PrivateKey privateKey, PublicKey publicKey) { 19 | this.keyId = keyId; 20 | this.privateKey = privateKey; 21 | this.publicKey = publicKey; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/keystore/KeyStore.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.keystore; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * KeyStore is store for keys used for signing and verifying of the signatures of the id_tokens. 7 | * 8 | * @author Ianislav Nachev 9 | */ 10 | public interface KeyStore { 11 | 12 | /** 13 | * Gets a list of available getKeys for signing the data and verifying it. 14 | * 15 | * @return a list of getKeys or an empty list if no getKeys are available 16 | */ 17 | List getKeys(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "token", 7 | srcs = glob(["*.kt", "*.java"]), 8 | deps = [ 9 | "@maven//:com_google_guava_guava", 10 | "//oauth2-server/src/main/java/com/clouway/oauth2/common", 11 | "//oauth2-server/src/main/java/com/clouway/oauth2/client", 12 | "//oauth2-server/src/main/java/com/clouway/oauth2/keystore", 13 | "//oauth2-server/src/main/java/com/clouway/oauth2/jws", 14 | "//oauth2-server/src/main/java/com/clouway/oauth2/jwt", 15 | "@maven//:io_jsonwebtoken_jjwt_api", 16 | "@maven//:io_jsonwebtoken_jjwt_impl", 17 | "@maven//:io_jsonwebtoken_jjwt_jackson", 18 | ] 19 | ) -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/BearerToken.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | import com.clouway.oauth2.common.DateTime; 4 | import com.google.common.base.Objects; 5 | 6 | import java.io.Serializable; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | /** 11 | * @author Ivan Stefanov 12 | */ 13 | public final class BearerToken implements Serializable { 14 | public final String value; 15 | public final GrantType grantType; 16 | public final String identityId; 17 | public final String clientId; 18 | public final String email; 19 | public final Map params; 20 | public final Set scopes; 21 | private final DateTime expiresAt; 22 | 23 | public BearerToken() { 24 | this(null, null, null, null, null, null, null, null); 25 | } 26 | 27 | public BearerToken(String value, GrantType grantType, String identityId, String clientId, String email, Set scopes, DateTime expiresAt, Map params) { 28 | this.value = value; 29 | this.grantType = grantType; 30 | this.identityId = identityId; 31 | this.clientId = clientId; 32 | this.email = email; 33 | this.scopes = scopes; 34 | this.expiresAt = expiresAt; 35 | this.params = params; 36 | } 37 | 38 | /** 39 | * Checks whether the Token expires at the provided instant time. 40 | * 41 | * @param instant the instant to against witch instant is checked 42 | * @return true if token expires at the provided time and false in other case 43 | */ 44 | public boolean expiresAt(DateTime instant) { 45 | return instant.after(expiresAt); 46 | } 47 | 48 | /** 49 | * Expiration time as timestamp value. 50 | * 51 | * @return the expiration time as timestamp 52 | */ 53 | public Long expirationTimestamp() { 54 | return expiresAt.timestamp(); 55 | } 56 | 57 | /** 58 | * Gets time to live in seconds of the current token. 59 | * 60 | * @param instant the instant time used for check 61 | * @return the time to live in seconds 62 | */ 63 | public Long ttlSeconds(DateTime instant) { 64 | return (expiresAt.timestamp() - instant.timestamp()) / 1000; 65 | } 66 | 67 | @Override 68 | public boolean equals(Object o) { 69 | if (this == o) return true; 70 | if (o == null || getClass() != o.getClass()) return false; 71 | BearerToken token = (BearerToken) o; 72 | return Objects.equal(value, token.value) && 73 | Objects.equal(identityId, token.identityId) && 74 | Objects.equal(clientId, token.clientId) && 75 | Objects.equal(expiresAt, token.expiresAt); 76 | } 77 | 78 | @Override 79 | public int hashCode() { 80 | return Objects.hashCode(value, clientId); 81 | } 82 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/FindIdentityRequest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | import com.clouway.oauth2.common.DateTime; 4 | import com.google.common.base.Objects; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * @author Ianislav Nachev 10 | */ 11 | public final class FindIdentityRequest { 12 | 13 | public final String identityId; 14 | public final GrantType grantType; 15 | public final DateTime instantTime; 16 | public final Map params; 17 | public final String clientId; 18 | 19 | public FindIdentityRequest(String identityId, GrantType grantType, DateTime instantTime, Map params, String clientId) { 20 | this.identityId = identityId; 21 | this.grantType = grantType; 22 | this.instantTime = instantTime; 23 | this.params = params; 24 | this.clientId = clientId; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if (this == o) return true; 30 | if (o == null || getClass() != o.getClass()) return false; 31 | FindIdentityRequest that = (FindIdentityRequest) o; 32 | return Objects.equal(identityId, that.identityId) && 33 | grantType == that.grantType && 34 | Objects.equal(instantTime, that.instantTime) && 35 | Objects.equal(params, that.params) && 36 | Objects.equal(clientId, that.clientId); 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | return Objects.hashCode(identityId, grantType, instantTime, params, clientId); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/GrantType.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | /** 4 | * GrantType is representing the type of the grant that was issued. 5 | * 6 | * @author Miroslav Genov (miroslav.genov@clouway.com) 7 | */ 8 | public enum GrantType { 9 | AUTHORIZATION_CODE, JWT 10 | } 11 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/IdTokenFactory.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | import com.clouway.oauth2.common.DateTime; 4 | import com.google.common.base.Optional; 5 | 6 | /** 7 | * A class that builds JWT tokens using JJWT builder 8 | *

9 | * 10 | * @author Vasil Mitov 11 | * 12 | */ 13 | public interface IdTokenFactory { 14 | 15 | /** 16 | * Creates a new id token from the provided metadata. 17 | * 18 | * @param host that requested the token 19 | * @param clientId the client id 20 | * @param identity identity to which the token is issued to 21 | * @param ttl time to live for the token 22 | * @param instant the time at which the token was requested 23 | * @return an encoded id token 24 | */ 25 | Optional create(String host, String clientId, Identity identity, Long ttl, DateTime instant); 26 | 27 | /** 28 | * Creates a new id token from the provided metadata. 29 | * 30 | * @param host that requested the token 31 | * @param clientId the client id 32 | * @param serviceAccount the service account to which the token is issued to 33 | * @param ttl time to live for the token 34 | * @param instant the time at which the token was requested 35 | * @return an encoded id token 36 | */ 37 | Optional create(String host, String clientId, ServiceAccount serviceAccount, Long ttl, DateTime instant); 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/Identity.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token 2 | 3 | /** 4 | * @author Miroslav Genov (miroslav.genov@clouway.com) 5 | */ 6 | data class Identity( 7 | val id: String, 8 | val name: String, 9 | val givenName: String, 10 | val familyName: String, 11 | val email: String, 12 | val picture: String?, 13 | val claims: Map, 14 | ) 15 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/IdentityFinder.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token 2 | 3 | /** 4 | * IdentityFinder is finding the Identity of the request. 5 | * 6 | * @author Mihail Lesikov (mlesikov@gmail.com) 7 | */ 8 | interface IdentityFinder { 9 | /** 10 | * Finds identity of the resource provider by providing it's id and the GrantType that was requested. 11 | * 12 | * @param request find identity request 13 | * @return the associated identity by that id or absent value if it's not available. 14 | */ 15 | fun findIdentity(request: FindIdentityRequest): FindIdentityResult 16 | } 17 | 18 | /** 19 | * FindIdentityResult is a result of the findIdentity operation. It can be either User, Client or NotFound depending on 20 | * the request. 21 | */ 22 | sealed interface FindIdentityResult { 23 | data class User( 24 | val identity: Identity, 25 | ) : FindIdentityResult 26 | 27 | data class ServiceAccountClient( 28 | val serviceAccount: ServiceAccount, 29 | ) : FindIdentityResult 30 | 31 | object NotFound : FindIdentityResult 32 | 33 | data class Error( 34 | val message: String, 35 | ) : FindIdentityResult 36 | } 37 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/JjwtIdTokenFactory.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token 2 | 3 | import com.clouway.oauth2.common.DateTime 4 | import com.clouway.oauth2.keystore.IdentityKeyPair 5 | import com.clouway.oauth2.keystore.KeyStore 6 | import com.google.common.base.Optional 7 | import io.jsonwebtoken.Jwts 8 | import io.jsonwebtoken.SignatureAlgorithm 9 | import java.util.Date 10 | import java.util.Random 11 | 12 | /** 13 | * @author Vasil Mitov @clouway.com> 14 | */ 15 | class JjwtIdTokenFactory( 16 | private val keyStore: KeyStore, 17 | ) : IdTokenFactory { 18 | override fun create( 19 | host: String, 20 | clientId: String, 21 | identity: Identity, 22 | ttl: Long, 23 | instant: DateTime, 24 | ): Optional { 25 | val keys = keyStore.keys 26 | 27 | if (keys == null || keys.isEmpty()) { 28 | return Optional.absent() 29 | } 30 | 31 | val signingKey = randomKey(keys) 32 | 33 | val claims: MutableMap = LinkedHashMap() 34 | claims["iss"] = host 35 | claims["aud"] = clientId 36 | claims["sub"] = identity.id 37 | claims["name"] = identity.name 38 | claims["email"] = identity.email 39 | claims["given_name"] = identity.givenName 40 | claims["family_name"] = identity.familyName 41 | claims.putAll(identity.claims) 42 | 43 | return Optional.of( 44 | Jwts 45 | .builder() 46 | .setHeaderParam( 47 | "cid", 48 | signingKey.keyId, 49 | ) // CertificateId - the ID of the certificate that the token was signed with. 50 | .setClaims(claims) 51 | .setIssuedAt(Date(instant.timestamp())) 52 | .setExpiration(Date(instant.timestamp() + ttl)) 53 | .signWith(SignatureAlgorithm.RS256, signingKey.privateKey) 54 | .compact(), 55 | ) 56 | } 57 | 58 | override fun create( 59 | host: String, 60 | clientId: String, 61 | serviceAccount: ServiceAccount, 62 | ttl: Long, 63 | instant: DateTime, 64 | ): Optional { 65 | val keys = keyStore.keys 66 | 67 | if (keys == null || keys.isEmpty()) { 68 | return Optional.absent() 69 | } 70 | 71 | val signingKey = randomKey(keys) 72 | 73 | val claims: MutableMap = LinkedHashMap() 74 | claims["iss"] = host 75 | claims["aud"] = clientId 76 | claims["sub"] = serviceAccount.clientId 77 | claims["name"] = serviceAccount.name 78 | claims["email"] = serviceAccount.clientEmail 79 | claims["given_name"] = "" 80 | claims["family_name"] = "" 81 | claims.putAll(serviceAccount.claims) 82 | 83 | return Optional.of( 84 | Jwts 85 | .builder() 86 | .setHeaderParam( 87 | "cid", 88 | signingKey.keyId, 89 | ) // CertificateId - the ID of the certificate that the token was signed with. 90 | .setClaims(claims) 91 | .setIssuedAt(Date(instant.timestamp())) 92 | .setExpiration(Date(instant.timestamp() + ttl)) 93 | .signWith(SignatureAlgorithm.RS256, signingKey.privateKey) 94 | .compact(), 95 | ) 96 | } 97 | 98 | private fun randomKey(keys: List): IdentityKeyPair { 99 | val random = Random() 100 | return keys[random.nextInt(keys.size)] 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/ServiceAccount.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token 2 | 3 | /** 4 | * ServiceAccount is a representation of a service account that is used to access a service. 5 | * 6 | * @author Miroslav Genov (miroslav.genov@clouway.com) 7 | */ 8 | data class ServiceAccount( 9 | val clientId: String, 10 | val clientEmail: String, 11 | val name: String, 12 | val customerId: Long?, 13 | val claims: Map, 14 | val permissions: Permissions = Permissions(allowAll = true, scopes = emptyList()), // Default to allow all 15 | ) 16 | 17 | data class Permissions( 18 | val allowAll: Boolean = false, // If true, overrides other constraints and allows all operations 19 | val scopes: List = emptyList(), // Specific scopes allowed for this service account 20 | val resourceConstraints: List = emptyList(), // Constraints on specific resources 21 | ) 22 | 23 | data class Scope( 24 | val name: String, // Name of the scope (e.g., "read", "write", "admin") 25 | val description: String? = null, // Optional description of the scope 26 | ) 27 | 28 | data class ResourceConstraint( 29 | val resourceType: String, // The type of resource being constrained (e.g., "bucket", "database") 30 | val resourceId: String, // The specific resource ID (e.g., "bucket123", "db456") 31 | val actions: List = emptyList(), // Actions allowed on this resource (e.g., "read", "write") 32 | ) 33 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/TokenErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | /** 4 | * @author Ivan Stefanov 5 | */ 6 | public class TokenErrorResponse extends RuntimeException { 7 | public final String code; 8 | public final String description; 9 | 10 | public TokenErrorResponse(String code, String description) { 11 | this.code = code; 12 | this.description = description; 13 | } 14 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/TokenGenerator.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | /** 4 | * @author Ivan Stefanov 5 | */ 6 | public interface TokenGenerator { 7 | String generate(); 8 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/TokenRequest.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token 2 | 3 | import com.clouway.oauth2.client.Client 4 | import com.clouway.oauth2.common.DateTime 5 | 6 | /** 7 | * @author Ivan Stefanov @clouway.com> 8 | */ 9 | data class TokenRequest( 10 | val grantType: GrantType, 11 | val client: Client, 12 | val identity: Identity? = null, 13 | val serviceAccount: ServiceAccount? = null, 14 | val scopes: Set, 15 | val `when`: DateTime, 16 | val params: Map, 17 | ) 18 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/TokenResponse.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | /** 4 | * TokenResponse is a response which is returned when token is issued or refreshed. 5 | *

6 | * Sometimes token cannot be issue dueerror and this is indicated using the {@link TokenResponse#isSuccessful()} method 7 | * call. 8 | * 9 | * @author Miroslav Genov (miroslav.genov@clouway.com) 10 | */ 11 | public final class TokenResponse { 12 | private final boolean successful; 13 | 14 | public final BearerToken accessToken; 15 | public final String refreshToken; 16 | 17 | public TokenResponse(boolean successful, BearerToken accessToken, String refreshToken) { 18 | this.successful = successful; 19 | this.refreshToken = refreshToken; 20 | this.accessToken = accessToken; 21 | } 22 | 23 | public boolean isSuccessful() { 24 | return successful; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/Tokens.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | import com.clouway.oauth2.common.DateTime; 4 | import com.google.common.base.Optional; 5 | 6 | /** 7 | * Tokens is responsible for issuing and retriving of issued tokens. 8 | * 9 | * @author Ivan Stefanov 10 | */ 11 | public interface Tokens { 12 | 13 | /** 14 | * Find token which is not expired till the provided time. 15 | * 16 | * @param token then token for which is looked 17 | * @return an optional token value or absent value if not present 18 | */ 19 | Optional findTokenAvailableAt(String token, DateTime when); 20 | 21 | /** 22 | * Refreshes token using the access token. 23 | * 24 | * @param token the access token 25 | * @param when token is going to be refreshed 26 | * @return the refreshed token 27 | */ 28 | TokenResponse refreshToken(String token, DateTime when); 29 | 30 | /** 31 | * Issues a new token for the provided identity. 32 | * 33 | * @param tokenRequest: 34 | * grantType type of the taken to be issued - JWT or Bearer 35 | * client the client to which token will be issued 36 | * identity the identity for which token was issued 37 | * scopes requested scopes 38 | * when the requested time on which it should be issued 39 | * params 40 | * @return the newly issued token 41 | */ 42 | TokenResponse issueToken(TokenRequest tokenRequest); 43 | 44 | /** 45 | * Revokes token from repository. 46 | * 47 | * @param token the token which to be revoked 48 | */ 49 | void revokeToken(String token); 50 | 51 | 52 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/UrlSafeTokenGenerator.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | import com.google.common.io.BaseEncoding; 4 | 5 | import java.util.UUID; 6 | 7 | /** 8 | * UrlSafeTokenGenerator is a token generator which is encoding randomly generated tokens in Base64Url format to ensure 9 | * that the generated tokens could be passed as URL parameters. 10 | *

11 | * It also removes the right padded '=' character, to prevent parameter collision. 12 | * 13 | * @author Ivan Stefanov 14 | */ 15 | public class UrlSafeTokenGenerator implements TokenGenerator { 16 | 17 | /** 18 | * Generates a new URL safe token. 19 | * 20 | * @return the newly generated token. 21 | */ 22 | @Override 23 | public String generate() { 24 | String generatedToken = BaseEncoding.base64Url().encode(UUID.randomUUID().toString().getBytes()); 25 | return trimRight(generatedToken, '='); 26 | } 27 | 28 | /** 29 | * Trim returns string with all leading and trailing Unicode code points contained in cutset removed. 30 | */ 31 | private String trimRight(String text, char cutset) { 32 | int i = text.length() - 1; 33 | while (i >= 0 && cutset == text.charAt(i)) { 34 | i--; 35 | } 36 | return text.substring(0, i + 1); 37 | } 38 | } -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/token/User.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | /** 4 | * @author Mihail Lesikov (mlesikov@gmail.com) 5 | */ 6 | public class User { 7 | public final String id; 8 | public final String email; 9 | public final String name; 10 | 11 | public User(String id,String email, String name) { 12 | this.id = id; 13 | this.email = email; 14 | this.name = name; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (!(o instanceof User)) return false; 21 | 22 | User user = (User) o; 23 | 24 | if (email != null ? !email.equals(user.email) : user.email != null) return false; 25 | if (id != null ? !id.equals(user.id) : user.id != null) return false; 26 | if (name != null ? !name.equals(user.name) : user.name != null) return false; 27 | 28 | return true; 29 | } 30 | 31 | @Override 32 | public int hashCode() { 33 | int result = id != null ? id.hashCode() : 0; 34 | result = 31 * result + (email != null ? email.hashCode() : 0); 35 | result = 31 * result + (name != null ? name.hashCode() : 0); 36 | return result; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/util/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "util", 7 | srcs = glob(["*.kt", "*.java"]), 8 | deps = [ 9 | "@maven//:com_clouway_fserve_fserve", 10 | ], 11 | ) -------------------------------------------------------------------------------- /oauth2-server/src/main/java/com/clouway/oauth2/util/Params.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.util; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.clouway.friendlyserve.RequestExt; 5 | import com.google.common.base.Strings; 6 | import com.google.common.collect.Maps; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * @author Mihail Lesikov (mlesikov@gmail.com) 14 | */ 15 | public class Params { 16 | public Map parse(Request request, String... exclusions) { 17 | 18 | RequestExt req = (RequestExt) request; 19 | 20 | List exclusionsList = Arrays.asList(exclusions); 21 | 22 | Map params = Maps.newHashMap(); 23 | 24 | for (String key : req.params().keySet()) { 25 | String value = request.param(key) == null ? "" : request.param(key); 26 | if (!Strings.isNullOrEmpty(value) && !exclusionsList.contains(key)) { 27 | params.put(key, value); 28 | } 29 | } 30 | 31 | return params; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/AuthorizationResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.oauth2.authorization.AuthorizationResponse; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | import static org.hamcrest.core.Is.is; 8 | 9 | /** 10 | * @author Ivan Stefanov 11 | */ 12 | public class AuthorizationResponseTest { 13 | @Test 14 | public void buildRedirectURI() throws Exception { 15 | AuthorizationResponse response = new AuthorizationResponse("123456789", "http://abv.bg/auth"); 16 | 17 | assertThat(response.buildURI(), is("http://abv.bg/auth?code=123456789")); 18 | } 19 | 20 | @Test 21 | public void buildAnotherRedirectURI() throws Exception { 22 | AuthorizationResponse response = new AuthorizationResponse("987654321", "http://zazz.bg/"); 23 | 24 | assertThat(response.buildURI(), is("http://zazz.bg/?code=987654321")); 25 | } 26 | 27 | @Test 28 | public void codeIsUrlSafelyEncoded() throws Exception { 29 | AuthorizationResponse response = new AuthorizationResponse( 30 | "a test 23", 31 | "http://zazz.bg/" 32 | ); 33 | 34 | assertThat(response.buildURI(), is("http://zazz.bg/?code=a+test+23")); 35 | } 36 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/ByteRequest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.google.common.collect.ImmutableMap; 5 | import com.google.common.collect.Lists; 6 | 7 | import java.io.ByteArrayInputStream; 8 | import java.io.InputStream; 9 | import java.util.Arrays; 10 | import java.util.LinkedHashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * @author Miroslav Genov (miroslav.genov@clouway.com) 15 | */ 16 | public class ByteRequest implements Request { 17 | 18 | private final Map params; 19 | private final Map headers; 20 | private final byte[] body; 21 | private final String path; 22 | 23 | public ByteRequest(String path, Map params, Map headers, byte[] content) { 24 | this.path = path; 25 | this.params = ImmutableMap.copyOf(params); 26 | this.body = Arrays.copyOf(content, content.length); 27 | this.headers = ImmutableMap.copyOf(headers); 28 | } 29 | 30 | public ByteRequest(Map params, Map headers) { 31 | this("/", params, headers, new byte[]{}); 32 | } 33 | 34 | public ByteRequest(String path, Map params, byte[] content) { 35 | this(path, params, new LinkedHashMap(), content); 36 | } 37 | 38 | 39 | @Override 40 | public String path() { 41 | return path; 42 | } 43 | 44 | @Override 45 | public String param(String name) { 46 | return params.get(name); 47 | } 48 | 49 | @Override 50 | public Iterable names() { 51 | return params.keySet(); 52 | } 53 | 54 | @Override 55 | public Iterable cookie(String name) { 56 | return Lists.newLinkedList(); 57 | } 58 | 59 | @Override 60 | public String header(String name) { 61 | return headers.get(name); 62 | } 63 | 64 | @Override 65 | public InputStream body() { 66 | return new ByteArrayInputStream(body); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/ClientEqualityTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.oauth2.client.Client; 4 | import org.junit.Test; 5 | 6 | import java.util.Collections; 7 | 8 | import static org.hamcrest.Matchers.is; 9 | import static org.hamcrest.Matchers.not; 10 | import static org.junit.Assert.assertThat; 11 | 12 | /** 13 | * @author Ivan Stefanov 14 | */ 15 | public class ClientEqualityTest { 16 | @Test 17 | public void areEqual() { 18 | Client client1 = new Client("id", "secret", "description", Collections.singleton("redirectURI"), true); 19 | Client client2 = new Client("id", "secret", "description", Collections.singleton("redirectURI"), true); 20 | 21 | assertThat(client1, is(client2)); 22 | } 23 | 24 | @Test 25 | public void areNotEqual() { 26 | Client client1 = new Client("id1", "secret1", "description1", Collections.singleton("redirectURI1"), false); 27 | Client client2 = new Client("id2", "secret2", "description2", Collections.singleton("redirectURI2"), false); 28 | 29 | assertThat(client1, is(not(client2))); 30 | } 31 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/DecodeClientCredentialsTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import com.clouway.friendlyserve.Response; 5 | import com.clouway.oauth2.client.ClientCredentials; 6 | import com.clouway.oauth2.common.DateTime; 7 | import com.google.common.collect.ImmutableMap; 8 | import org.jmock.Expectations; 9 | import org.jmock.integration.junit4.JUnitRuleMockery; 10 | import org.junit.Rule; 11 | import org.junit.Test; 12 | 13 | import java.net.HttpURLConnection; 14 | 15 | import static com.clouway.friendlyserve.testing.FakeRequest.aNewRequest; 16 | import static org.hamcrest.Matchers.equalTo; 17 | import static org.hamcrest.Matchers.is; 18 | import static org.junit.Assert.assertThat; 19 | 20 | /** 21 | * @author Miroslav Genov (miroslav.genov@clouway.com) 22 | */ 23 | public class DecodeClientCredentialsTest { 24 | 25 | @Rule 26 | public JUnitRuleMockery context = new JUnitRuleMockery(); 27 | 28 | private ClientRequest clientRequest = context.mock(ClientRequest.class); 29 | 30 | private ClientAuthenticationCredentialsRequest handler = new ClientAuthenticationCredentialsRequest(clientRequest); 31 | 32 | @Test 33 | public void happyPath() { 34 | final DateTime anyInstantTime = new DateTime(); 35 | final Request clientAuthRequest = clientAuthRequest( 36 | "Basic ZmU3MjcyMmE0MGRlODQ2ZTAzODY1Y2IzYjU4MmFlZDU3ODQxYWM3MTo4NTc2MTNkYjdiMTgyMzJjNzJhNTA5M2FkMTlkYmM2ZGY3NGExMzll" 37 | ); 38 | 39 | context.checking(new Expectations() {{ 40 | oneOf(clientRequest).handleAsOf( 41 | clientAuthRequest, new ClientCredentials( 42 | "fe72722a40de846e03865cb3b582aed57841ac71", 43 | "857613db7b18232c72a5093ad19dbc6df74a139e" 44 | ), anyInstantTime); 45 | }}); 46 | 47 | 48 | handler.handleAsOf(clientAuthRequest, anyInstantTime); 49 | } 50 | 51 | @Test 52 | public void headerIsNotForBasicAuthorization() { 53 | final Request clientAuthRequest = clientAuthRequest( 54 | "::some header value::" 55 | ); 56 | Response response = handler.handleAsOf(clientAuthRequest, anyInstantTime()); 57 | assertThat(response.status().code, is(equalTo(HttpURLConnection.HTTP_BAD_REQUEST))); 58 | } 59 | 60 | @Test 61 | public void basicHeaderIsNotEncodedWithBase64() { 62 | final Request clientAuthRequest = clientAuthRequest( 63 | "Basic z" 64 | ); 65 | Response response = handler.handleAsOf(clientAuthRequest, anyInstantTime()); 66 | assertThat(response.status().code, is(equalTo(HttpURLConnection.HTTP_BAD_REQUEST))); 67 | } 68 | 69 | @Test 70 | public void clientIdAndClientSecretAreNotCorrectlySeparated() { 71 | final Request clientAuthRequest = clientAuthRequest( 72 | "Basic dGVzdGlkdGVzdHNlY3JldA==" // is encoding of: testidtestsecret 73 | ); 74 | Response response = handler.handleAsOf(clientAuthRequest, anyInstantTime()); 75 | assertThat(response.status().code, is(equalTo(HttpURLConnection.HTTP_BAD_REQUEST))); 76 | } 77 | 78 | @Test 79 | public void authenticatePublicClient() throws Exception { 80 | final Request clientAuthRequest = aNewRequest().param("client_id", "::client id::").build(); 81 | final DateTime anyInstantTime = new DateTime(); 82 | 83 | context.checking(new Expectations() {{ 84 | oneOf(clientRequest).handleAsOf( 85 | clientAuthRequest, new ClientCredentials( 86 | "::client id::", 87 | "" 88 | ), anyInstantTime); 89 | }}); 90 | 91 | handler.handleAsOf(clientAuthRequest, anyInstantTime); 92 | } 93 | 94 | private DateTime anyInstantTime() { 95 | return new DateTime(); 96 | } 97 | 98 | private Request clientAuthRequest(String authorizationValue) { 99 | return new ByteRequest( 100 | ImmutableMap.of(), 101 | ImmutableMap.of("Authorization", authorizationValue) 102 | ); 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/IdentityControllerTest.kt: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2 2 | 3 | import com.clouway.friendlyserve.Request 4 | import com.clouway.friendlyserve.RsText 5 | import com.clouway.friendlyserve.testing.ParamRequest 6 | import com.clouway.friendlyserve.testing.RsPrint 7 | import com.clouway.oauth2.common.DateTime 8 | import com.google.common.base.Optional 9 | import com.google.common.collect.ImmutableMap 10 | import org.hamcrest.Matchers 11 | import org.jmock.Expectations 12 | import org.jmock.auto.Mock 13 | import org.jmock.integration.junit4.JUnitRuleMockery 14 | import org.junit.Assert 15 | import org.junit.Rule 16 | import org.junit.Test 17 | import java.io.IOException 18 | import java.net.HttpURLConnection 19 | import java.net.URLDecoder 20 | import java.net.URLEncoder 21 | import javax.annotation.Resource 22 | import kotlin.Throws 23 | 24 | /** 25 | * @author Miroslav Genov (miroslav.genov@clouway.com) 26 | */ 27 | class IdentityControllerTest { 28 | @Rule 29 | @JvmField 30 | var context = JUnitRuleMockery() 31 | 32 | private val identityFinder = context.mock(ResourceOwnerIdentityFinder::class.java) 33 | private val identityActivity = context.mock(IdentityActivity::class.java) 34 | 35 | @Test 36 | @Throws(IOException::class) 37 | fun happyPath() { 38 | val identityController = IdentityController(identityFinder, identityActivity, "") 39 | val request: Request = ParamRequest( 40 | ImmutableMap.of( 41 | "client_id", "::client_id::" 42 | ) 43 | ) 44 | val anyInstantTime = DateTime() 45 | context.checking(object : Expectations() { 46 | init { 47 | oneOf(identityFinder).find(request, anyInstantTime) 48 | will(returnValue(Optional.of("::identity_id::"))) 49 | oneOf(identityActivity).execute("::identity_id::", request, anyInstantTime) 50 | will(returnValue(RsText("test response"))) 51 | } 52 | }) 53 | val response = identityController.handleAsOf(request, anyInstantTime) 54 | Assert.assertThat(RsPrint(response).printBody(), Matchers.`is`(Matchers.equalTo("test response"))) 55 | } 56 | 57 | @Test 58 | @Throws(IOException::class) 59 | fun userWasNotAuthorized() { 60 | val identityController = IdentityController(identityFinder, identityActivity, "/r/oauth/login?continue=") 61 | val request: Request = ParamRequest(ImmutableMap.of("client_id", "::client1::")) 62 | val anyInstantTime = DateTime() 63 | context.checking(object : Expectations() { 64 | init { 65 | oneOf(identityFinder).find(request, anyInstantTime) 66 | will(returnValue(Optional.absent())) 67 | } 68 | }) 69 | val response = identityController.handleAsOf(request, anyInstantTime) 70 | val status = response.status() 71 | Assert.assertThat(status.code, Matchers.`is`(Matchers.equalTo(HttpURLConnection.HTTP_MOVED_TEMP))) 72 | Assert.assertThat( 73 | status.redirectUrl, 74 | Matchers.`is`(Matchers.equalTo("/r/oauth/login?continue=%2F%3Fclient_id%3D%3A%3Aclient1%3A%3A")) 75 | ) 76 | } 77 | 78 | @Test 79 | @Throws(IOException::class) 80 | fun promptConsentWasRequested() { 81 | val identityController = IdentityController(identityFinder, identityActivity, "/r/oauth/login?continue=") 82 | val request: Request = ParamRequest(mapOf( 83 | "response_type" to "code", 84 | "redirect_uri" to "https://portal/o/oauth2/callback", 85 | "state" to "mystate1", 86 | "prompt" to "consent" 87 | )) 88 | val anyInstantTime = DateTime() 89 | context.checking(object : Expectations() { 90 | init { 91 | never(identityFinder) 92 | } 93 | }) 94 | val response = identityController.handleAsOf(request, anyInstantTime) 95 | val status = response.status() 96 | Assert.assertThat(status.code, Matchers.`is`(Matchers.equalTo(HttpURLConnection.HTTP_MOVED_TEMP))) 97 | Assert.assertThat( 98 | URLDecoder.decode(status.redirectUrl, "UTF-8"), 99 | Matchers.`is`(Matchers.equalTo("/r/oauth/login?continue=/?response_type=code&redirect_uri=https://portal/o/oauth2/callback&state=mystate1")) 100 | ) 101 | } 102 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/RetrievePublicCertsTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Response; 4 | import com.clouway.friendlyserve.testing.RsPrint; 5 | import com.clouway.oauth2.jws.Pem; 6 | import com.clouway.oauth2.keystore.IdentityKeyPair; 7 | import com.clouway.oauth2.keystore.KeyStore; 8 | import com.clouway.oauth2.util.PemKeyGenerator; 9 | import com.google.gson.JsonObject; 10 | import org.junit.Test; 11 | 12 | import java.io.ByteArrayInputStream; 13 | import java.security.KeyPair; 14 | import java.util.Arrays; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | import static org.hamcrest.Matchers.is; 19 | import static org.junit.Assert.assertThat; 20 | 21 | /** 22 | * @author Ianislav Nachev 23 | */ 24 | public class RetrievePublicCertsTest { 25 | 26 | @Test 27 | public void fewPublicCerts() throws Exception { 28 | final KeyPair keyPair = PemKeyGenerator.generatePair(); 29 | 30 | PublicCertsController controller = new PublicCertsController( 31 | new FakeKeyStore(Arrays.asList( 32 | new IdentityKeyPair("key1", keyPair.getPrivate(), keyPair.getPublic()), 33 | new IdentityKeyPair("key2", keyPair.getPrivate(), keyPair.getPublic()), 34 | new IdentityKeyPair("key3", keyPair.getPrivate(), keyPair.getPublic()) 35 | )) 36 | ); 37 | 38 | Response resp = controller.ack(null); 39 | JsonObject response = new RsPrint(resp).asJson(); 40 | 41 | Pem pem = new Pem(); 42 | String firstPemKey = response.get("key1").getAsString(); 43 | String secondPemKey = response.get("key2").getAsString(); 44 | String thirdPemKey = response.get("key3").getAsString(); 45 | 46 | pem.parse(new ByteArrayInputStream(firstPemKey.getBytes())); 47 | pem.parse(new ByteArrayInputStream(secondPemKey.getBytes())); 48 | pem.parse(new ByteArrayInputStream(thirdPemKey.getBytes())); 49 | } 50 | 51 | @Test 52 | public void noPublicCerts() throws Exception { 53 | PublicCertsController controller = new PublicCertsController(new FakeKeyStore(Collections.emptyList())); 54 | Response resp = controller.ack(null); 55 | RsPrint response = new RsPrint(resp); 56 | 57 | assertThat(response.printBody(), is("{}")); 58 | } 59 | 60 | private class FakeKeyStore implements KeyStore { 61 | private final List keys; 62 | 63 | public FakeKeyStore(List keys) { 64 | this.keys = keys; 65 | } 66 | 67 | 68 | @Override 69 | public List getKeys() { 70 | return keys; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/SerializeBearerTokensTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2; 2 | 3 | import com.clouway.friendlyserve.Response; 4 | import com.clouway.friendlyserve.testing.RsPrint; 5 | import com.google.common.collect.Sets; 6 | import org.junit.Test; 7 | 8 | import java.io.IOException; 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | 12 | import static org.hamcrest.Matchers.equalTo; 13 | import static org.hamcrest.Matchers.is; 14 | import static org.junit.Assert.assertThat; 15 | 16 | /** 17 | * @author Miroslav Genov (miroslav.genov@clouway.com) 18 | */ 19 | public class SerializeBearerTokensTest { 20 | 21 | @Test 22 | public void happyPath() throws IOException { 23 | 24 | String expectedResponse = "Content-Type: application/json; charset=utf-8\r\n" + 25 | "{\"access_token\":\"mF_9.B5f-4.1JqM\"," + 26 | "\"token_type\":\"Bearer\"," + 27 | "\"expires_in\":3600," + 28 | "\"scope\":\"scope1 scope2\"," + 29 | "\"refresh_token\":\"tGzv3JOkF0XG5Qx2TlKWIA\"," + 30 | "\"id_token\":\"::id token::\"}"; 31 | 32 | assertThat(contentOf(new BearerTokenResponse("mF_9.B5f-4.1JqM", 3600L, Sets.newTreeSet(Arrays.asList("scope1", "scope2")), "tGzv3JOkF0XG5Qx2TlKWIA", "::id token::")), is(equalTo(expectedResponse))); 33 | } 34 | 35 | @Test 36 | public void anotherToken() throws IOException { 37 | 38 | String expectedResponse = "Content-Type: application/json; charset=utf-8\r\n" + 39 | "{\"access_token\":\"::token2::\"," + 40 | "\"token_type\":\"Bearer\"," + 41 | "\"expires_in\":2400," + 42 | "\"refresh_token\":" + 43 | "\"::refresh_token::2\"," + 44 | "\"id_token\":\"::id token::\"}"; 45 | 46 | assertThat(contentOf(new BearerTokenResponse("::token2::", 2400L, Collections.emptySet(), "::refresh_token::2", "::id token::")), is(equalTo(expectedResponse))); 47 | } 48 | 49 | private String contentOf(Response response) throws IOException { 50 | return new RsPrint(response).print(); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/authorization/AuthorizationBuilder.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.authorization; 2 | 3 | import com.clouway.oauth2.codechallenge.CodeChallenge; 4 | import com.google.common.collect.Maps; 5 | 6 | import java.util.Collections; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | /** 11 | * @author Miroslav Genov (miroslav.genov@clouway.com) 12 | */ 13 | public class AuthorizationBuilder { 14 | private String responseType = "code"; 15 | private String code = "::code::"; 16 | private String clientId = "::client id::"; 17 | private Set scopes = Collections.singleton("scope1"); 18 | private Set redirectURIs = Collections.singleton("redirectURI"); 19 | private String identityId = "::any identity::"; 20 | private CodeChallenge codeChallenge = new CodeChallenge("", ""); 21 | private Map params = Maps.newHashMap(); 22 | 23 | public static AuthorizationBuilder newAuthorization() { 24 | return new AuthorizationBuilder(); 25 | } 26 | 27 | public AuthorizationBuilder withCode(String code) { 28 | this.code = code; 29 | return this; 30 | } 31 | 32 | public AuthorizationBuilder withCodeChallenge(CodeChallenge codeChallenge) { 33 | this.codeChallenge = codeChallenge; 34 | return this; 35 | } 36 | 37 | public AuthorizationBuilder withId(String id) { 38 | this.identityId = id; 39 | return this; 40 | } 41 | 42 | public AuthorizationBuilder addParam(String name, String value) { 43 | this.params.put(name, value); 44 | return this; 45 | } 46 | 47 | public Authorization build() { 48 | return new Authorization(responseType, clientId, identityId, code, scopes, redirectURIs, codeChallenge, params); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/authorization/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "authorization", 7 | srcs = glob([ 8 | "*.kt", 9 | "*.java", 10 | ]), 11 | deps = [ 12 | "//oauth2-server/src/main/java/com/clouway/oauth2/authorization", 13 | "//oauth2-server/src/main/java/com/clouway/oauth2/codechallenge", 14 | "@maven//:com_google_guava_guava", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/client/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | kt_jvm_library( 6 | name = "client", 7 | srcs = glob([ 8 | "*.kt", 9 | "*.java", 10 | ]), 11 | deps = [ 12 | "//oauth2-server/src/main/java/com/clouway/oauth2/client", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/client/ClientBuilder.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.client; 2 | 3 | import java.util.Collections; 4 | 5 | /** 6 | * @author Miroslav Genov (miroslav.genov@clouway.com) 7 | */ 8 | public class ClientBuilder { 9 | 10 | public static ClientBuilder aNewClient() { 11 | return new ClientBuilder(); 12 | } 13 | 14 | private String clientId; 15 | private String redirectUrl = "::redirect_url::"; 16 | private String clientSecret; 17 | private boolean publicOne = false; 18 | 19 | public ClientBuilder withId(String clientId) { 20 | this.clientId = clientId; 21 | return this; 22 | } 23 | 24 | public ClientBuilder withSecret(String clientSecret) { 25 | this.clientSecret = clientSecret; 26 | return this; 27 | } 28 | 29 | public ClientBuilder withRedirectUrl(String redirectUrl) { 30 | this.redirectUrl = redirectUrl; 31 | return this; 32 | } 33 | 34 | public ClientBuilder publicOne() { 35 | this.publicOne = true; 36 | return this; 37 | } 38 | 39 | public Client build() { 40 | return new Client(clientId, clientSecret, "::desc::", Collections.singleton(redirectUrl), publicOne); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/codechallenge/AuthorizationCodeVerifierTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.codechallenge; 2 | 3 | import com.google.common.io.BaseEncoding; 4 | import org.junit.Test; 5 | 6 | import java.nio.charset.StandardCharsets; 7 | import java.security.MessageDigest; 8 | 9 | import static org.junit.Assert.assertFalse; 10 | import static org.junit.Assert.assertTrue; 11 | 12 | /** 13 | * @author Vasil Mitov 14 | */ 15 | public class AuthorizationCodeVerifierTest { 16 | 17 | private CodeVerifier codeVerifier = new AuthorizationCodeVerifier(); 18 | 19 | @Test 20 | public void happyPath() throws Exception { 21 | String cv = "By8FlXFBNZv5YseXWj0cPP5WxtkdMMOGeucC8uUszVYq9FFfdfR96D1M9kQzdAEQ3GvbGg85LbrPwuZYgZ0oP93BuGvjHGOEGRRYAGzMrzAvWcGPbVWDySDQXHTHuHEF"; 22 | 23 | CodeChallenge codeChallenge = new CodeChallenge("u3ifVLR4EQrCCLSIuFe52yZlHED17juKU3WbKxfnV6c", "S256"); 24 | assertTrue(codeVerifier.verify(codeChallenge, cv)); 25 | } 26 | 27 | @Test 28 | public void wrongCodeVerifierValue() throws Exception { 29 | String codeVerifierValue = "::code::"; 30 | byte[] hashedCodeChallenge = MessageDigest.getInstance("SHA-256").digest(codeVerifierValue.getBytes(StandardCharsets.UTF_8)); 31 | String transformedCodeChallenge = BaseEncoding.base64Url().encode(hashedCodeChallenge); 32 | 33 | CodeChallenge codeChallenge = new CodeChallenge(transformedCodeChallenge, "S256"); 34 | 35 | assertFalse(codeVerifier.verify(codeChallenge, "::codee::")); 36 | } 37 | 38 | @Test 39 | public void usingPlainMethod() throws Exception { 40 | CodeChallenge codeChallenge = new CodeChallenge("::code::", "plain"); 41 | assertTrue(codeVerifier.verify(codeChallenge, "::code::")); 42 | } 43 | 44 | @Test 45 | public void codeVerifierProvidedButNoCodeChallengeFound() throws Exception { 46 | assertFalse(codeVerifier.verify(new CodeChallenge("", ""), "::code::")); 47 | } 48 | 49 | @Test 50 | public void codeChallengeProvidedButNoCodeVerifier() throws Exception { 51 | CodeChallenge codeChallenge = new CodeChallenge("::code::", "plain"); 52 | assertFalse(codeVerifier.verify(codeChallenge, "")); 53 | } 54 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/codechallenge/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test") 2 | load("//tools/jvm:variables.bzl", "TEST_DEPS") 3 | 4 | kt_jvm_test( 5 | name = "AuthorizationCodeVerifierTest", 6 | srcs = ["AuthorizationCodeVerifierTest.java"], 7 | test_class = "com.clouway.oauth2.codechallenge.AuthorizationCodeVerifierTest", 8 | deps = TEST_DEPS + [ 9 | "//oauth2-server/src/main/java/com/clouway/oauth2/authorization", 10 | "@maven//:com_google_guava_guava", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/common/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test") 2 | load("//tools/jvm:variables.bzl", "TEST_DEPS") 3 | 4 | PACKAGE_DEPS = [ 5 | "//oauth2-server/src/main/java/com/clouway/oauth2/common", 6 | "//oauth2-server/src/main/java/com/clouway/oauth2/util", 7 | ] 8 | 9 | kt_jvm_library( 10 | name = "common", 11 | srcs = ["CalendarUtil.java", "CommonMatchers.java"], 12 | visibility = ["//visibility:public"], 13 | deps = TEST_DEPS + PACKAGE_DEPS, 14 | ) 15 | 16 | kt_jvm_test( 17 | name = "DateTimeAddSecondsTest", 18 | srcs = ["DateTimeAddSecondsTest.java"], 19 | test_class = "com.clouway.oauth2.common.DateTimeAddSecondsTest", 20 | deps = TEST_DEPS + PACKAGE_DEPS + [":common"], 21 | ) 22 | 23 | kt_jvm_test( 24 | name = "DurationEqualityTest", 25 | srcs = ["DurationEqualityTest.java"], 26 | test_class = "com.clouway.oauth2.common.DurationEqualityTest", 27 | deps = TEST_DEPS + PACKAGE_DEPS, 28 | ) 29 | 30 | kt_jvm_test( 31 | name = "DurationTest", 32 | srcs = ["DurationTest.java"], 33 | test_class = "com.clouway.oauth2.common.DurationTest", 34 | deps = TEST_DEPS + PACKAGE_DEPS, 35 | ) 36 | 37 | kt_jvm_test( 38 | name = "DateTimeEqualityTest", 39 | srcs = ["DateTimeEqualityTest.java"], 40 | test_class = "com.clouway.oauth2.common.DateTimeEqualityTest", 41 | deps = TEST_DEPS + PACKAGE_DEPS, 42 | ) 43 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/common/CalendarUtil.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.common; 2 | 3 | import java.util.Calendar; 4 | import java.util.Date; 5 | 6 | /** 7 | * @author Miroslav Genov (miroslav.genov@clouway.com) 8 | */ 9 | public class CalendarUtil { 10 | 11 | public static Date newTime(int hour, int minute, int seconds) { 12 | Calendar calendar = Calendar.getInstance(); 13 | calendar.set(Calendar.HOUR_OF_DAY, hour); 14 | calendar.set(Calendar.MINUTE, minute); 15 | calendar.set(Calendar.SECOND, seconds); 16 | return calendar.getTime(); 17 | } 18 | 19 | public static DateTime newDateTime(int year, int month, int day, int hour, int minute, int seconds) { 20 | Calendar calendar = Calendar.getInstance(); 21 | calendar.set(Calendar.DAY_OF_YEAR, year); 22 | calendar.set(Calendar.MONTH, month - 1); 23 | calendar.set(Calendar.DAY_OF_MONTH, day); 24 | calendar.set(Calendar.HOUR_OF_DAY, hour); 25 | calendar.set(Calendar.MINUTE, minute); 26 | calendar.set(Calendar.SECOND, seconds); 27 | calendar.set(Calendar.MILLISECOND, 0); 28 | return new DateTime(calendar.getTime()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/common/CommonMatchers.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.common; 2 | 3 | import org.hamcrest.Description; 4 | import org.hamcrest.Matcher; 5 | import org.hamcrest.TypeSafeMatcher; 6 | 7 | import java.util.Calendar; 8 | import java.util.Objects; 9 | 10 | /** 11 | * @author Miroslav Genov (miroslav.genov@clouway.com) 12 | */ 13 | public class CommonMatchers { 14 | 15 | public static Matcher timeIsAt(final int hour, final int minute, final int second) { 16 | return new TypeSafeMatcher() { 17 | @Override 18 | protected boolean matchesSafely(DateTime time) { 19 | 20 | Calendar calendar = Calendar.getInstance(); 21 | calendar.setTime(time.asDate()); 22 | 23 | return calendar.get(Calendar.HOUR_OF_DAY) == hour 24 | && calendar.get(Calendar.MINUTE) == minute 25 | && calendar.get(Calendar.SECOND) == second; 26 | } 27 | 28 | @Override 29 | public void describeTo(Description description) { 30 | 31 | } 32 | }; 33 | } 34 | 35 | public static Matcher matching(final T value) { 36 | return new TypeSafeMatcher() { 37 | @Override 38 | protected boolean matchesSafely(T t) { 39 | return Objects.deepEquals(value, t); 40 | } 41 | 42 | @Override 43 | public void describeTo(Description description) { 44 | } 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/common/DateTimeAddSecondsTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.common; 2 | 3 | import org.junit.Test; 4 | 5 | import static com.clouway.oauth2.common.CalendarUtil.newTime; 6 | import static com.clouway.oauth2.common.CommonMatchers.timeIsAt; 7 | import static org.junit.Assert.assertThat; 8 | 9 | /** 10 | * @author Miroslav Genov (miroslav.genov@clouway.com) 11 | */ 12 | public class DateTimeAddSecondsTest { 13 | 14 | @Test 15 | public void add30Seconds() { 16 | assertThat(new DateTime(newTime(23, 45, 10)).plusSeconds(30), timeIsAt(23, 45, 40)); 17 | } 18 | 19 | @Test 20 | public void add120Seconds() { 21 | assertThat(new DateTime(newTime(23, 45, 10)).plusSeconds(120), timeIsAt(23, 47, 10)); 22 | } 23 | 24 | @Test 25 | public void subSecondsByAddingNegative() { 26 | assertThat(new DateTime(newTime(23, 45, 10)).plusSeconds(-1), timeIsAt(23, 45, 9)); 27 | } 28 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/common/DateTimeEqualityTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.common; 2 | 3 | import com.clouway.oauth2.common.DateTime; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.Matchers.equalTo; 7 | import static org.hamcrest.Matchers.is; 8 | import static org.hamcrest.Matchers.not; 9 | import static org.junit.Assert.assertThat; 10 | 11 | /** 12 | * @author Miroslav Genov (miroslav.genov@clouway.com) 13 | */ 14 | public class DateTimeEqualityTest { 15 | 16 | @Test 17 | public void areEqual() { 18 | assertThat(new DateTime(1L), is(equalTo(new DateTime(1L)))); 19 | } 20 | 21 | @Test 22 | public void areNotEqual() { 23 | assertThat(new DateTime(1L), is(not(equalTo(new DateTime(2L))))); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/common/DurationEqualityTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.common; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.Matchers.is; 6 | import static org.hamcrest.Matchers.not; 7 | import static org.junit.Assert.assertThat; 8 | 9 | /** 10 | * @author Ivan Stefanov 11 | */ 12 | public class DurationEqualityTest { 13 | @Test 14 | public void areEqual() { 15 | Duration duration1 = new Duration(1000L); 16 | Duration duration2 = new Duration(1000L); 17 | 18 | assertThat(duration1, is(duration2)); 19 | } 20 | 21 | @Test 22 | public void areNotEqual() { 23 | Duration duration1 = new Duration(1000L); 24 | Duration duration2 = new Duration(2000L); 25 | 26 | assertThat(duration1, is(not(duration2))); 27 | } 28 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/common/DurationTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.common; 2 | 3 | import org.junit.Test; 4 | 5 | import static com.clouway.oauth2.common.Duration.hours; 6 | import static com.clouway.oauth2.common.Duration.minutes; 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | import static org.hamcrest.core.Is.is; 9 | 10 | /** 11 | * @author Ivan Stefanov 12 | */ 13 | public class DurationTest { 14 | @Test 15 | public void minutesCalculation() throws Exception { 16 | Duration expectedDuration = minutes(60); 17 | Duration actualDuration = new Duration(3600L); 18 | 19 | assertThat(expectedDuration, is(actualDuration)); 20 | } 21 | 22 | @Test 23 | public void hoursCalculation() throws Exception { 24 | Duration expectedDuration = hours(24); 25 | Duration actualDuration = new Duration(86400L); 26 | 27 | assertThat(expectedDuration, is(actualDuration)); 28 | } 29 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/jws/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test") 2 | load("//tools/jvm:variables.bzl", "TEST_DEPS") 3 | 4 | kt_jvm_test( 5 | name = "PemTest", 6 | srcs = ["PemTest.java"], 7 | test_class = "com.clouway.oauth2.jws.PemTest", 8 | deps = TEST_DEPS + [ 9 | "//oauth2-server/src/main/java/com/clouway/oauth2/jws", 10 | "@maven//:com_google_guava_guava", 11 | ], 12 | ) 13 | 14 | kt_jvm_test( 15 | name = "ReadPemFilesTest", 16 | srcs = ["ReadPemFilesTest.java"], 17 | data = glob(["testdata/*.pem"]), 18 | test_class = "com.clouway.oauth2.jws.ReadPemFilesTest", 19 | deps = TEST_DEPS + [ 20 | "//oauth2-server/src/main/java/com/clouway/oauth2/jws", 21 | "@maven//:com_google_guava_guava", 22 | ], 23 | ) 24 | 25 | kt_jvm_test( 26 | name = "VerifySignaturesWithRsaTest", 27 | srcs = ["VerifySignaturesWithRsaTest.java"], 28 | test_class = "com.clouway.oauth2.jws.VerifySignaturesWithRsaTest", 29 | deps = TEST_DEPS + [ 30 | "//oauth2-server/src/main/java/com/clouway/oauth2/jws", 31 | "@maven//:com_google_guava_guava", 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/jws/testdata/secret.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCIga1VBV5S+diJ 3 | 2abh4YVHAWb7rlM9pnK1GVZOYE6xheJ1jsoxh03lUHjw8HtBR2USIAWG1a/kAAdz 4 | fngrFFudpOO5RU1yEFnz2WNwy7gj3xu1erqtxmctOkyGA2hfATzHUfStSl8gyMXK 5 | f3GURe4m+Y/qa1nI0ClGrJWbFdM9gz9YjRf3rAJsYZR17dGyBRIdlMeKF/QudP68 6 | Dv/2/fssOhrIz5RZl2pkovWYidZxeRBl9XS9naoUZM8HvqoyUTE7ByjvDBR9qBkS 7 | mCXJAUb82/LkQFLhufhnHCSC8b+kHWpBhiMqslVnR00mhiQHCW7PNtLfb6ugTZNi 8 | cfBIaI1DAgMBAAECggEAEwXnTtrhqzSQPZ2sSPwxo5SJcnd3uDay85PlWCTJsqmS 9 | xokwmjhd3aAaSpFoy88UQbNescyjp2VtpGWyf2Zl4hExfwcuZL/smTPpTLXHIpCb 10 | /u1siH0GseHW+jINYHf+rVQ5gdDEcwAnuDGMdXpNVvceXC+7omWH6wZwDt26w26L 11 | J31/aHwvjrlIbQq4PAS0gAh6CHUgyeOv8MkCR5rVhS9Db03U6QFRc79CMMghl906 12 | C9rqRjDmIX3MEh/IiW/hJzmz8kPNL1RiUb5OI24ceDtWa2AzvGalwDS0SvtS8SBT 13 | 9jI0htKl8NGY6oknfkopw6LuVjZ2hOdgV3z26DJ2YQKBgQDbUlAnqdSLrtzepKZN 14 | PJQqNgC2ATUVvpcE64ze48t8sV5lVsdceLllzVxD0hjh6ngqynDGJjzp+AEcS5LQ 15 | nCYinVb0x8iIZlC5dIPDP85mIfYARFFHF20DD0HoMJfcxqeeJsaU24Pkh7Y8V/BC 16 | as4DM8ByOreBk2a/VZaMWA9X8wKBgQCfVdqHBeZDOSoHPHZ9ApMsHAYJmp/0MBzv 17 | vQda4lZehmJ9Hefr5J3tk4c4xH+FODAEqaWHoFe+Y95XuR69xkGiBiIHlhMK1xSA 18 | 1Gmn+WWPTjhPcNqGWXfeTnMZNWgZguo7e10fHNWQT2RklegJfB593E4K83Y+pG+b 19 | 4adiHMQZcQKBgCuI9lI5OvCTQFKNmllAiiSq3Y9DRBdR4sZeP3NLAmx5BMTW6fHo 20 | IN0dW5A21yuZEEtmLeaXVoYW7ZmBQt5X8JX0Z3tlYN/6d1Go2DLcqorJePxqkzuq 21 | YcA2uh1t7+cqI8GX7tlDjbXCXqExz4ZPjx9BmZTTJPP6n22hfqXTIRCTAoGAC498 22 | Gn3YFhqIrRu68RkFupaR7ZJ1do8jGlXZucNgRt1zOea4lAnzV3BzyC+hnPXVrhDs 23 | /KkqlJrEYBMDYvuGeY3+XBSMbyXpy+sde12B++LN/R2QDV1icBO7ECIq2mcAPa6W 24 | tBIwgJbyDsY9nqqNv84DL5I4ixT9MA8wSNMTe1ECgYB329OIU8nIpn4KQDiFlNdg 25 | VE+F2I7f4zNtM/NAeKoG7WGY6CO2sGmKKvT7g5Ql5/mbzsowkzaLNrzR0RQNEy5r 26 | IsCHV4InIHYnIPxuxT2nWP33x1rlso7pB5xTf5q0ee+6ps25zWmBfEYlM72dPFCi 27 | e16DdKBIzOaDt8ShlblC9g== 28 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/token/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test") 2 | load("//tools/jvm:variables.bzl", "FSERVE_TEST_DEPS", "TEST_DEPS") 3 | 4 | kt_jvm_library( 5 | name = "token", 6 | srcs = [ 7 | "BearerTokenBuilder.java", 8 | "IdentityBuilder.java", 9 | ], 10 | visibility = ["//visibility:public"], 11 | deps = [ 12 | "//oauth2-server/src/main/java/com/clouway/oauth2/token", 13 | ], 14 | ) 15 | 16 | kt_jvm_test( 17 | name = "VerifyingJjwtTokensTest", 18 | srcs = ["VerifyingJjwtTokensTest.kt"], 19 | test_class = "com.clouway.oauth2.token.VerifyingJjwtTokensTest", 20 | deps = TEST_DEPS + FSERVE_TEST_DEPS + [ 21 | "//oauth2-server/src/main/java/com/clouway/oauth2/jws", 22 | "//oauth2-server/src/main/java/com/clouway/oauth2/token", 23 | "@maven//:com_google_guava_guava", 24 | ], 25 | ) 26 | 27 | kt_jvm_test( 28 | name = "UrlSafeTokenGeneratorTest", 29 | srcs = ["UrlSafeTokenGeneratorTest.java"], 30 | test_class = "com.clouway.oauth2.token.UrlSafeTokenGeneratorTest", 31 | deps = TEST_DEPS + [ 32 | ":token", 33 | ], 34 | ) 35 | 36 | kt_jvm_test( 37 | name = "JjwtIdTokenFactoryTest", 38 | srcs = ["JjwtIdTokenFactoryTest.java"], 39 | test_class = "com.clouway.oauth2.token.JjwtIdTokenFactoryTest", 40 | deps = TEST_DEPS + FSERVE_TEST_DEPS + [ 41 | ":token", 42 | "//oauth2-server/src/main/java/com/clouway/oauth2/jws", 43 | "//oauth2-server/src/main/java/com/clouway/oauth2/token", 44 | "//oauth2-server/src/test/java/com/clouway/oauth2/common", 45 | "//oauth2-server/src/test/java/com/clouway/oauth2/util", 46 | "@maven//:com_google_guava_guava", 47 | ], 48 | ) 49 | 50 | kt_jvm_test( 51 | name = "TokenEqualityTest", 52 | srcs = ["TokenEqualityTest.java"], 53 | test_class = "com.clouway.oauth2.token.TokenEqualityTest", 54 | deps = TEST_DEPS + [ 55 | "//oauth2-server/src/main/java/com/clouway/oauth2/token", 56 | "@maven//:com_google_guava_guava", 57 | ], 58 | ) 59 | 60 | kt_jvm_test( 61 | name = "TokenIsExpiredAtTest", 62 | srcs = ["TokenIsExpiredAtTest.java"], 63 | test_class = "com.clouway.oauth2.token.TokenIsExpiredAtTest", 64 | deps = TEST_DEPS + [ 65 | ":token", 66 | "//oauth2-server/src/main/java/com/clouway/oauth2/token", 67 | "//oauth2-server/src/test/java/com/clouway/oauth2/common", 68 | "@maven//:com_google_guava_guava", 69 | ], 70 | ) 71 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/token/BearerTokenBuilder.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | import com.clouway.oauth2.common.DateTime; 4 | 5 | import java.util.Collections; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author Miroslav Genov (miroslav.genov@clouway.com) 10 | */ 11 | public class BearerTokenBuilder { 12 | 13 | public static BearerTokenBuilder aNewToken() { 14 | return new BearerTokenBuilder(); 15 | } 16 | 17 | private String clientId = ""; 18 | private String identityId = ""; 19 | private GrantType grantType = GrantType.AUTHORIZATION_CODE; 20 | private DateTime expiresAt = new DateTime(); 21 | private String value; 22 | private String email = ""; 23 | private Map params = Collections.emptyMap(); 24 | 25 | public BearerTokenBuilder withValue(String value) { 26 | this.value = value; 27 | return this; 28 | } 29 | 30 | public BearerTokenBuilder expiresAt(DateTime expiresAt) { 31 | this.expiresAt = expiresAt; 32 | return this; 33 | } 34 | 35 | public BearerTokenBuilder withEmail(String email) { 36 | this.email = email; 37 | return this; 38 | } 39 | 40 | public BearerTokenBuilder params(Map params) { 41 | this.params = params; 42 | return this; 43 | } 44 | 45 | public BearerTokenBuilder identityId(String identityId){ 46 | this.identityId = identityId; 47 | return this; 48 | } 49 | 50 | public BearerTokenBuilder grantType(GrantType grantType){ 51 | this.grantType = grantType; 52 | return this; 53 | } 54 | 55 | public BearerToken build() { 56 | return new BearerToken(value, grantType, identityId, clientId, email, Collections.emptySet(), expiresAt, params); 57 | } 58 | 59 | public BearerTokenBuilder forClient(String clientId) { 60 | this.clientId = clientId; 61 | return this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/token/IdentityBuilder.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author Ianislav Nachev 8 | */ 9 | public class IdentityBuilder { 10 | Map claims = new LinkedHashMap<>(); 11 | 12 | public static IdentityBuilder aNewIdentity() { 13 | return new IdentityBuilder(); 14 | } 15 | 16 | private String id = ""; 17 | 18 | public IdentityBuilder withId(String id) { 19 | this.id = id; 20 | return this; 21 | } 22 | 23 | public Identity build() { 24 | return new Identity(id, "", "", "", "", "", claims); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/token/TokenEqualityTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | import com.clouway.oauth2.common.DateTime; 4 | import org.junit.Test; 5 | 6 | import java.util.Collections; 7 | import java.util.Date; 8 | 9 | import static com.google.common.collect.ImmutableMap.of; 10 | import static org.hamcrest.Matchers.is; 11 | import static org.hamcrest.Matchers.not; 12 | import static org.junit.Assert.assertThat; 13 | 14 | /** 15 | * @author Ivan Stefanov 16 | */ 17 | public class TokenEqualityTest { 18 | 19 | @Test 20 | public void areEqual() { 21 | DateTime creationDate = new DateTime(); 22 | BearerToken token1 = new BearerToken("value", GrantType.AUTHORIZATION_CODE, "identityId", "::client id::", "::email::", Collections.emptySet(), creationDate, of("::index::", "::1::")); 23 | BearerToken token2 = new BearerToken("value", GrantType.AUTHORIZATION_CODE, "identityId", "::client id::", "::email::", Collections.emptySet(), creationDate, of("::index::", "::1::")); 24 | 25 | assertThat(token1, is(token2)); 26 | } 27 | 28 | @Test 29 | public void areNotEqual() { 30 | DateTime creationDate = new DateTime(); 31 | BearerToken token1 = new BearerToken("value1", GrantType.AUTHORIZATION_CODE, "identityId", "::client id::", "::email::", Collections.emptySet(), creationDate, of("::index::", "::1::")); 32 | BearerToken token2 = new BearerToken("value2", GrantType.AUTHORIZATION_CODE, "identityId", "::client id::", "::email::", Collections.emptySet(), creationDate, of("::index::", "::1::")); 33 | 34 | assertThat(token1, is(not(token2))); 35 | } 36 | 37 | @Test 38 | public void areNotEqualWhenDifferentExpirationTimes() { 39 | BearerToken token1 = new BearerToken("value", GrantType.AUTHORIZATION_CODE, "identityId", "::client id::", "::email::", Collections.emptySet(), new DateTime(new Date(1408532291030L)), of("::index::", "::1::")); 40 | BearerToken token2 = new BearerToken("value", GrantType.AUTHORIZATION_CODE, "identityId", "::client id::", "::email::", Collections.emptySet(), new DateTime(new Date(1408532291031L)), of("::index::", "::1::")); 41 | 42 | assertThat(token1, is(not(token2))); 43 | } 44 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/token/TokenIsExpiredAtTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | import com.clouway.oauth2.common.DateTime; 4 | import org.junit.Test; 5 | 6 | import static com.clouway.oauth2.common.CalendarUtil.newTime; 7 | import static com.clouway.oauth2.token.BearerTokenBuilder.aNewToken; 8 | import static org.hamcrest.Matchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | /** 12 | * @author Miroslav Genov (miroslav.genov@clouway.com) 13 | */ 14 | public class TokenIsExpiredAtTest { 15 | 16 | @Test 17 | public void isStillActive() { 18 | BearerToken token = aNewToken().expiresAt(new DateTime(newTime(10, 0, 0))).build(); 19 | assertThat(token.expiresAt(new DateTime(newTime(9, 59, 59))), is(false)); 20 | } 21 | 22 | @Test 23 | public void wasExpired() { 24 | BearerToken token = aNewToken().expiresAt(new DateTime(newTime(10, 0, 0))).build(); 25 | assertThat(token.expiresAt(new DateTime(newTime(10, 1, 1))), is(true)); 26 | } 27 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/token/UrlSafeTokenGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.token; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.junit.Test; 5 | 6 | import java.util.Set; 7 | 8 | import static org.hamcrest.Matchers.equalTo; 9 | import static org.hamcrest.Matchers.is; 10 | import static org.hamcrest.Matchers.not; 11 | import static org.hamcrest.Matchers.nullValue; 12 | import static org.junit.Assert.assertThat; 13 | 14 | /** 15 | * @author Miroslav Genov (miroslav.genov@clouway.com) 16 | */ 17 | public class UrlSafeTokenGeneratorTest { 18 | 19 | @Test 20 | public void happyPath() { 21 | String token = new UrlSafeTokenGenerator().generate(); 22 | assertThat(token,is(not(nullValue()))); 23 | } 24 | 25 | @Test 26 | public void uniqueTokensAreGenerated() { 27 | UrlSafeTokenGenerator tokenGenerator = new UrlSafeTokenGenerator(); 28 | Set tokens = Sets.newHashSet(); 29 | for (int i = 0; i < 10;i++) { 30 | tokens.add(tokenGenerator.generate()); 31 | } 32 | assertThat(tokens.size(), is(equalTo(10))); 33 | } 34 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/util/ArgumentCaptor.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.util; 2 | 3 | import org.hamcrest.BaseMatcher; 4 | import org.hamcrest.Description; 5 | 6 | /** 7 | * Used for capturing the arguments of a invoked method of a mocked object using jmock. 8 | * 9 | * 10 | * final ArgumentCaptor captor = new ArgumentCaptor(); 11 | * context.checking(new Expectations() {{ 12 | * oneOf(mock).addInvoicePaymentChargedEventHandler(with(captor)); 13 | * }}); 14 | * 15 | * ...some method invocation of a tested class... 16 | * 17 | * assertThat(captor.getValue(), is(equalTo("expected string value"))); 18 | * 19 | * 20 | * @author Ivan Lazov 21 | */ 22 | public class ArgumentCaptor extends BaseMatcher { 23 | 24 | private T instance; 25 | 26 | public T getValue() { 27 | return instance; 28 | } 29 | 30 | @SuppressWarnings("unchecked") 31 | public boolean matches(Object o) { 32 | try { 33 | instance = (T) o; 34 | return true; 35 | } catch (ClassCastException ex) { 36 | return false; 37 | } 38 | } 39 | 40 | public void describeTo(Description description) { 41 | } 42 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/util/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test") 2 | load("//tools/jvm:variables.bzl", "FSERVE_TEST_DEPS", "TEST_DEPS") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | kt_jvm_library( 7 | name = "util", 8 | srcs = glob(["*.java"]), 9 | deps = FSERVE_TEST_DEPS + TEST_DEPS + [ 10 | "//oauth2-server/src/main/java/com/clouway/oauth2/util", 11 | "//oauth2-server/src/main/java/com/clouway/oauth2/common", 12 | "//oauth2-server/src/main/java/com/clouway/oauth2/jws", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/util/ParamsTest.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.util; 2 | 3 | import com.clouway.friendlyserve.Request; 4 | import org.hamcrest.CoreMatchers; 5 | import org.junit.Test; 6 | 7 | import java.util.Map; 8 | 9 | import static com.clouway.friendlyserve.testing.FakeRequest.aNewRequest; 10 | import static com.google.common.collect.ImmutableMap.of; 11 | import static org.junit.Assert.assertThat; 12 | 13 | /** 14 | * @author Mihail Lesikov (mlesikov@gmail.com) 15 | */ 16 | public class ParamsTest { 17 | @Test 18 | public void parse() throws Exception { 19 | Map params = new Params().parse(aNewRequest().param("::index::", "::1::").build()); 20 | assertThat(params, CoreMatchers.>is(of("::index::", "::1::"))); 21 | } 22 | 23 | @Test 24 | public void excludeParams() throws Exception { 25 | Request request = aNewRequest().param("::i::", "::1::").param("::codeparam::", "::1::").param("::otherparam::", "::1::").build(); 26 | Map params = new Params().parse(request, "::codeparam::", "::otherparam::"); 27 | assertThat(params, CoreMatchers.>is(of("::i::", "::1::"))); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /oauth2-server/src/test/java/com/clouway/oauth2/util/PemKeyGenerator.java: -------------------------------------------------------------------------------- 1 | package com.clouway.oauth2.util; 2 | 3 | import com.clouway.oauth2.jws.Pem; 4 | 5 | import java.security.KeyPair; 6 | import java.security.KeyPairGenerator; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.security.PrivateKey; 9 | import java.security.PublicKey; 10 | import java.util.Collections; 11 | 12 | /** 13 | * @author Vasil Mitov 14 | */ 15 | public class PemKeyGenerator { 16 | private static KeyPair keyPair = getKeyPair(2048); 17 | 18 | public static KeyPair generatePair() { 19 | return getKeyPair(2048); 20 | } 21 | 22 | public static Pem.Block generatePublicKey() { 23 | PublicKey publicKey = keyPair.getPublic(); 24 | return new Pem.Block("PUBLIC KEY", Collections.emptyMap(), publicKey.getEncoded()); 25 | } 26 | 27 | public static Pem.Block generatePrivateKey() { 28 | PrivateKey privateKey = keyPair.getPrivate(); 29 | return new Pem.Block("PRIVATE KEY", Collections.emptyMap(), privateKey.getEncoded()); 30 | } 31 | 32 | private static KeyPair getKeyPair(Integer size) { 33 | KeyPairGenerator keyGen = null; 34 | try { 35 | keyGen = KeyPairGenerator.getInstance("RSA"); 36 | } catch (NoSuchAlgorithmException e) { 37 | e.printStackTrace(); 38 | } 39 | keyGen.initialize(size); 40 | return keyGen.generateKeyPair(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tools/jvm/BUILD.bazel: -------------------------------------------------------------------------------- 1 | 2 | package(default_visibility = ["//visibility:public"]) 3 | 4 | exports_files([ 5 | "variables.bzl", 6 | ]) -------------------------------------------------------------------------------- /tools/jvm/variables.bzl: -------------------------------------------------------------------------------- 1 | TEST_DEPS = [ 2 | "@maven//:org_hamcrest_hamcrest_all", 3 | "@maven//:junit_junit", 4 | "@maven//:org_jmock_jmock", 5 | "@maven//:org_jmock_jmock_junit4", 6 | "@maven//:io_mockk_mockk_jvm", 7 | "@maven//:nl_jqno_equalsverifier_equalsverifier", 8 | ] 9 | 10 | FSERVE_TEST_DEPS = [ 11 | "@maven//:com_clouway_fserve_testing", 12 | ] 13 | --------------------------------------------------------------------------------