diff --git a/.github/workflows/maven-deploy.yml b/.github/_old-maven-deploy.yml similarity index 100% rename from .github/workflows/maven-deploy.yml rename to .github/_old-maven-deploy.yml diff --git a/.github/workflows/maven-deploy-release.yml b/.github/workflows/maven-deploy-release.yml new file mode 100644 index 0000000..ed702c9 --- /dev/null +++ b/.github/workflows/maven-deploy-release.yml @@ -0,0 +1,45 @@ +name: Maven Deploy Release +on: + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + services: + mysql: + image: mariadb:latest + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: test + MYSQL_USER: test + MYSQL_PASSWORD: test + MYSQL_RANDOM_ROOT_PASSWORD: yes + ports: + - 3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build + run: mvn -B -DbuildVersion=${{ github.event.release.tag_name }} package -Dmaven.test.skip=true + - name: Test + run: mvn -B test + env: + MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} + MYSQL_USERNAME: test + MYSQL_PASSWORD: test + - name: Install GPG Key + run: echo -e "$GPG_PRIVATE_KEY" | gpg --import --no-tty --batch --yes + env: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + - name: Deploy to JavaWebStack Repository + run: mvn deploy -B -DbuildVersion=${{ github.event.release.tag_name }} -s build/settings.xml -Dmaven.test.skip=true + env: + DEPLOYMENT_USERNAME: ${{ secrets.DEPLOYMENT_USERNAME }} + DEPLOYMENT_PASSWORD: ${{ secrets.DEPLOYMENT_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} diff --git a/README.md b/README.md index de479e6..c5d6322 100644 --- a/README.md +++ b/README.md @@ -15,20 +15,44 @@ Passport is a JWS-Module which allows you to create easily Authentication in you ### Example usage ```java -class MyApp extends WebApplication { +import org.javawebstack.passport.services.oauth2.InteraAppsOAuth2Service; + +class MyApp { /* ... */ - protected void setupModules() { + protected void setup() { + HTTPServer httpServer = new HTTPServer().port(1234); + + Passport passport = new Passport("/auth"); + OAuth2Strategy oAuth2Strategy = new OAuth2Strategy("http://localhost:1234"); + oAuth2Strategy.setHttpCallbackHandler((e, callback) -> { + return "Hello " + callback.getProfile().getName(); + }); + + oAuth2Strategy.use("interaapps", new InteraAppsOAuth2Provider("myid", "mysecret").setScopes("user:read")); + + passport.use("oauth2", oAuth2Strategy); - OAuth2Module oAuth2Module = new OAuth2Module(); - oAuth2Module - .addService(new GithubOAuth2Service("", "", /*Redirect Host*/ "http://localhost:2222")) - .setOAuth2Callback((service, exchange, callback) -> { - System.out.println("Someone logged in with "+service); - return "Hello "+callback.getProfile().name; - }); - addModule(oAuth2Module); + passport.createRoutes(httpServer); + httpServer.start(); + + // Creates Routes: /auth/oauth2/interaapps, /auth/oauth2/interaapps/callback } + + // JWS-Passport ships also an abstracted form of handling oauth2 + public void oAuthWithoutHTTPServer() { + OAuth2Strategy oAuth2Strategy = new OAuth2Strategy("http://localhost:1234"); + oAuth2Strategy.use("interaapps", new InteraAppsOAuth2Provider("myid", "mysecret").setScopes("user:read")); + + // Redirect + String callbackUrl = ".../callback"; + String redirectUrl = oAuth2Strategy.get("interaapps").redirect(callbackUrl); + + // On callback + OAuth2Callback callback = oAuth2Strategy.get("interaapps").callback(new AbstractObject().set("code", code), callbackUrl); + System.out.println("Hello "+callback.getProfile().name); + } + /* ... */ } ``` @@ -41,19 +65,6 @@ class MyApp extends WebApplication { 1.0-SNAPSHOT ``` -#### or Jitpack -```xml - - jitpack.io - https://jitpack.io - - - - com.github.JavaWebStack - Passport - COMMIT_HASH - -``` # Services Service|Class|Control-Panel|More Information @@ -63,4 +74,4 @@ Google|GoogleOAuth2Service|[Google Developer Console](https://console.developers Discord|DiscordOAuth2Service|[Discord Developer Portal](https://discord.com/developers/applications)|- Facebook|FacebookOAuth2Service|[Facebook Developer Center](https://console.developers.google.com/)|TODO InteraApps|InteraAppsOAuth2Service|[IA-Accounts Developer Center](https://accounts.interaapps.de/developers/projects)|- -Twitch|TwitchOAuth2Service|[Twitch Developers](https://dev.twitch.tv/)|Implements the OAuth authorization code flow \ No newline at end of file +Twitch|TwitchOAuth2Service|[Twitch Developers](https://dev.twitch.tv/)|Implements the OAuth authorization code flow diff --git a/pom.xml b/pom.xml index 9f7ba19..01cd41f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,32 +4,52 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + Passport + Adds authentication with ease + https://github.com/JavaWebStack/passport + org.javawebstack Passport - 1.0-SNAPSHOT + ${buildVersion} 8 8 + 1.0.1-SNAPSHOT - - - javawebstack - https://repo.javawebstack.org - - + + + The Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + Julian Gojani + julian@gojani.xyz + JavaWebStack + https://javawebstack.org + + + + + scm:git:git://github.com/JavaWebStack/passport.git + scm:git:ssh://github.com:JavaWebStack/passport.git + https://github.com/JavaWebStack/passport/tree/master + org.javawebstack - Web-Framework - 1.0-SNAPSHOT + http-client + 1.0.0 org.javawebstack - HTTP-Client - 1.0-SNAPSHOT + http-server + 1.0.0 com.google.apis @@ -46,12 +66,17 @@ - org.apache.maven.plugins - maven-compiler-plugin - - 8 - 8 - + maven-deploy-plugin + 3.0.0-M1 + + + default-deploy + deploy + + deploy + + + org.apache.maven.plugins @@ -59,15 +84,51 @@ 2.22.1 - maven-deploy-plugin - 3.0.0-M1 + org.apache.maven.plugins + maven-source-plugin + 2.2.1 - default-deploy - deploy + attach-sources - deploy + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.0.1 + + + sign-artifacts + verify + + sign + + A313520526A8DFE1C2A30399C35A3D43C557B112 + gpg + + --no-tty + --batch + --yes + + @@ -76,12 +137,12 @@ - javawebstack-snapshots - https://nexus.lumaserv.cloud/repository/javawebstack-snapshots + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots - javawebstack-releases - https://nexus.lumaserv.cloud/repository/javawebstack-releases + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - \ No newline at end of file + diff --git a/src/main/java/org/javawebstack/passport/AuthService.java b/src/main/java/org/javawebstack/passport/AuthService.java deleted file mode 100644 index 97f56db..0000000 --- a/src/main/java/org/javawebstack/passport/AuthService.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.javawebstack.passport; - -import org.javawebstack.httpserver.HTTPServer; - -public interface AuthService { - String getName(); - - default void setupServer(HTTPServer server){} -} diff --git a/src/main/java/org/javawebstack/passport/OAuth2Module.java b/src/main/java/org/javawebstack/passport/OAuth2Module.java deleted file mode 100644 index e821ca5..0000000 --- a/src/main/java/org/javawebstack/passport/OAuth2Module.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.javawebstack.passport; - -import org.javawebstack.framework.WebApplication; -import org.javawebstack.framework.module.Module; -import org.javawebstack.httpserver.HTTPServer; -import org.javawebstack.orm.exception.ORMConfigurationException; -import org.javawebstack.orm.wrapper.SQL; -import org.javawebstack.passport.services.oauth2.OAuth2Callback; -import org.javawebstack.passport.services.oauth2.OAuth2CallbackHandler; -import org.javawebstack.passport.services.oauth2.OAuth2Service; - -import java.util.ArrayList; -import java.util.List; - -public class OAuth2Module implements Module { - - private final List services; - private String pathPrefix = "/authorization/oauth2/"; - - private OAuth2CallbackHandler oAuth2Callback = (service, exchange, callback)->null; - - public OAuth2Module(){ - services = new ArrayList<>(); - } - - public void beforeSetupServer(WebApplication application, HTTPServer server) { - services.forEach(service -> { - service.setupServer(server); - if (service instanceof OAuth2Service) { - server.get(pathPrefix+service.getName(), exchange -> ((OAuth2Service) service).redirect(exchange, this)); - server.get(pathPrefix+service.getName()+"/callback", exchange -> { - OAuth2Callback callback = ((OAuth2Service) service).callback(exchange, this); - - return oAuth2Callback.callback(service.getName(), exchange, callback); - }); - } - }); - } - - public void setupModels(WebApplication application, SQL sql) throws ORMConfigurationException { - - } - - public OAuth2Module addService(AuthService authService) { - services.add(authService); - return this; - } - - public List getServices() { - return services; - } - - public void setOAuth2Callback(OAuth2CallbackHandler oAuth2Callback) { - this.oAuth2Callback = oAuth2Callback; - } - - public OAuth2Module setPathPrefix(String pathPrefix) { - this.pathPrefix = pathPrefix; - return this; - } - - public String getPathPrefix() { - return pathPrefix; - } -} diff --git a/src/main/java/org/javawebstack/passport/Passport.java b/src/main/java/org/javawebstack/passport/Passport.java new file mode 100644 index 0000000..0cd5242 --- /dev/null +++ b/src/main/java/org/javawebstack/passport/Passport.java @@ -0,0 +1,42 @@ +package org.javawebstack.passport; + +import org.javawebstack.httpserver.HTTPServer; +import org.javawebstack.passport.strategies.Strategy; +import org.javawebstack.passport.strategies.oauth2.OAuth2Provider; +import org.javawebstack.passport.strategies.oauth2.OAuth2Strategy; + +import java.util.HashMap; +import java.util.Map; + +public class Passport { + + private Map strategies = new HashMap<>(); + private String prefixUrl = ""; + + public Passport() {} + + public Passport(String prefixUrl){ + this.prefixUrl = prefixUrl; + } + + public Passport use(String name, Strategy strategy){ + strategy.setPassport(this); + strategies.put(name, strategy); + return this; + } + + public Strategy get(String name){ + return strategies.get(name); + } + + public Passport createRoutes(HTTPServer server){ + strategies.forEach((name, strategy) -> { + strategy.createRoutes(prefixUrl+"/"+name, server); + }); + return this; + } + + public String getPrefixUrl() { + return prefixUrl; + } +} diff --git a/src/main/java/org/javawebstack/passport/models/PassportUser.java b/src/main/java/org/javawebstack/passport/models/PassportUser.java deleted file mode 100644 index 42dabe4..0000000 --- a/src/main/java/org/javawebstack/passport/models/PassportUser.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.javawebstack.passport.models; - -import org.javawebstack.orm.Model; -import org.javawebstack.orm.annotation.Column; -import org.javawebstack.orm.annotation.Dates; - -import java.sql.Timestamp; - -public interface PassportUser { - String setPassword(String unhashedPassword); - boolean checkPassword(String hashedPassword); -} diff --git a/src/main/java/org/javawebstack/passport/services/oauth2/DiscordOAuth2Service.java b/src/main/java/org/javawebstack/passport/services/oauth2/DiscordOAuth2Service.java deleted file mode 100644 index e1f261d..0000000 --- a/src/main/java/org/javawebstack/passport/services/oauth2/DiscordOAuth2Service.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.javawebstack.passport.services.oauth2; - -import com.google.gson.Gson; -import org.javawebstack.abstractdata.AbstractObject; -import org.javawebstack.abstractdata.util.QueryString; -import org.javawebstack.httpclient.HTTPClient; -import org.javawebstack.httpserver.Exchange; -import org.javawebstack.passport.OAuth2Module; -import org.javawebstack.passport.Profile; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; - - -public class DiscordOAuth2Service extends HTTPClient implements OAuth2Service { - private final String clientId; - private final String secret; - private String[] scopes = new String[]{"email", "identify"}; - private String redirectDomain; - - public DiscordOAuth2Service(String clientId, String secret, String redirectDomain){ - setBaseUrl("https://discord.com"); - this.clientId = clientId; - this.secret = secret; - this.redirectDomain = redirectDomain; - } - - public String getName() { - return "discord"; - } - - public DiscordOAuth2Service setScopes(String[] scopes) { - this.scopes = scopes; - return this; - } - - public OAuth2Callback callback(Exchange exchange, OAuth2Module oAuth2Module) { - AbstractObject abstractObject = post("/api/oauth2/token") - .header("User-Agent", "JWSPassportClient/1") - .formBodyString(new QueryString() - .set("client_id", clientId) - .set("client_secret", secret) - .set("code", exchange.rawRequest().getParameter("code")) - .set("grant_type", "authorization_code") - .set("redirect_uri", redirectDomain+oAuth2Module.getPathPrefix()+getName()+"/callback") - .set("scope", String.join(" ", scopes)) - .toString() - ) - .data().object(); - - return new OAuth2Callback( - abstractObject.get("access_token").string(), - getProfile(abstractObject.get("access_token").string()), - new HTTPClient("https://discord.com/").bearer(abstractObject.get("access_token").string())).setRefreshToken(abstractObject.get("refresh_token").string()); - } - - - public Object redirect(Exchange exchange, OAuth2Module oAuth2Module) { - try { - exchange.redirect("https://discord.com/api/oauth2/authorize?response_type=code&client_id="+clientId+"&prompt=consent&scope="+ URLEncoder.encode(String.join(" ", scopes), "UTF-8")+"&redirect_uri="+URLEncoder.encode(redirectDomain+oAuth2Module.getPathPrefix()+getName()+"/callback", "UTF-8")); - - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - return null; - } - return ""; - } - - @Override - public Profile getProfile(String accessToken) { - Profile profile = new Profile(); - - AbstractObject data = - get("/api/users/@me") - .bearer(accessToken) - .header("User-Agent", "JWSPassportClient/1") - .data().object(); - - if (data.has("id")) { - profile.id = data.get("id").string(); - - if (data.has("avatar")) - profile.avatar = "https://cdn.discordapp.com/avatars/"+profile.id+"/"+data.get("avatar").string()+".png"; - } - - if (data.has("username")) - profile.name = data.get("username").string(); - - if (data.has("email")) - profile.mail = data.get("email").string(); - - data.forEach(profile::set); - - return profile; - } - - -} diff --git a/src/main/java/org/javawebstack/passport/services/oauth2/FacebookOAuth2Service.java b/src/main/java/org/javawebstack/passport/services/oauth2/FacebookOAuth2Service.java deleted file mode 100644 index d68212e..0000000 --- a/src/main/java/org/javawebstack/passport/services/oauth2/FacebookOAuth2Service.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.javawebstack.passport.services.oauth2; - -import org.javawebstack.abstractdata.AbstractObject; -import org.javawebstack.abstractdata.util.QueryString; -import org.javawebstack.httpclient.HTTPClient; -import org.javawebstack.httpserver.Exchange; -import org.javawebstack.httpserver.helper.MimeType; -import org.javawebstack.passport.OAuth2Module; -import org.javawebstack.passport.Profile; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; - - -public class FacebookOAuth2Service extends HTTPClient implements OAuth2Service { - private final String clientId; - private final String secret; - private String[] scopes = new String[]{"read:user","user:email"}; - private String redirectDomain; - - public FacebookOAuth2Service(String clientId, String secret, String redirectDomain){ - setBaseUrl("https://api.github.com"); - this.clientId = clientId; - this.secret = secret; - this.redirectDomain = redirectDomain; - } - - public String getName() { - return "github"; - } - - public FacebookOAuth2Service setScopes(String[] scopes) { - this.scopes = scopes; - return this; - } - - public OAuth2Callback callback(Exchange exchange, OAuth2Module oAuth2Module) { - - AbstractObject abstractObject = new HTTPClient("https://github.com").post("/login/oauth/access_token") - .formBodyString(new QueryString() - .set("client_id", clientId) - .set("client_secret", secret) - .set("code", exchange.rawRequest().getParameter("code")) - .toString()) - .header("Accept", MimeType.JSON.getMimeTypes().get(0)) - .data().object(); - - - if (abstractObject.has("scope")/* && abstractObject.get("scope").string().equals("read:user,user:email")*/) { - Profile profile = new Profile(); - return new OAuth2Callback(abstractObject.get("access_token").string(), getProfile(abstractObject.get("access_token").string()), new HTTPClient("https://api.github.com").header("Authorization", "token "+abstractObject.get("access_token").string())); - } - - return null; - } - - - public Object redirect(Exchange exchange, OAuth2Module oAuth2Module) { - try { - exchange.redirect("https://www.facebook.com/v11.0/dialog/oauth?client_id="+clientId+"&scope="+ URLEncoder.encode(String.join(" ", scopes), "UTF-8")+"&redirect_uri="+URLEncoder.encode(redirectDomain+oAuth2Module.getPathPrefix()+getName()+"/callback", "UTF-8")); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - return null; - } - return ""; - } - - @Override - public Profile getProfile(String accessToken) { - Profile profile = new Profile(); - AbstractObject userData = get("/user") - .header("Authorization", "token "+accessToken) - .data().object(); - - if (userData.has("id")) - profile.id = userData.get("id").number().toString(); - if (userData.has("name")) - profile.name = userData.get("name").string(); - if (userData.has("avatar_url")) - profile.avatar = userData.get("avatar_url").string(); - - userData.forEach(profile::set); - - get("/user/emails") - .header("Authorization", "token "+accessToken) - .data().array().forEach(abstractElement -> { - if (profile.mail == null) { - profile.mail = abstractElement.object().get("email").string(); - } - }); - return profile; - } - - -} diff --git a/src/main/java/org/javawebstack/passport/services/oauth2/GithubOAuth2Service.java b/src/main/java/org/javawebstack/passport/services/oauth2/GithubOAuth2Service.java deleted file mode 100644 index f06f508..0000000 --- a/src/main/java/org/javawebstack/passport/services/oauth2/GithubOAuth2Service.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.javawebstack.passport.services.oauth2; - -import org.javawebstack.abstractdata.AbstractObject; -import org.javawebstack.abstractdata.util.QueryString; -import org.javawebstack.httpclient.HTTPClient; -import org.javawebstack.httpserver.Exchange; -import org.javawebstack.httpserver.helper.MimeType; -import org.javawebstack.passport.OAuth2Module; -import org.javawebstack.passport.Profile; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; - - -public class GithubOAuth2Service extends HTTPClient implements OAuth2Service { - private final String clientId; - private final String secret; - private String[] scopes = new String[]{"read:user","user:email"}; - private String redirectDomain; - - public GithubOAuth2Service(String clientId, String secret, String redirectDomain){ - setBaseUrl("https://api.github.com"); - this.clientId = clientId; - this.secret = secret; - this.redirectDomain = redirectDomain; - } - - public String getName() { - return "github"; - } - - public GithubOAuth2Service setScopes(String[] scopes) { - this.scopes = scopes; - return this; - } - - public OAuth2Callback callback(Exchange exchange, OAuth2Module oAuth2Module) { - - AbstractObject abstractObject = new HTTPClient("https://github.com").post("/login/oauth/access_token") - .formBodyString(new QueryString() - .set("client_id", clientId) - .set("client_secret", secret) - .set("code", exchange.rawRequest().getParameter("code")) - .toString()) - .header("Accept", MimeType.JSON.getMimeTypes().get(0)) - .data().object(); - - - if (abstractObject.has("scope")/* && abstractObject.get("scope").string().equals("read:user,user:email")*/) { - Profile profile = new Profile(); - return new OAuth2Callback(abstractObject.get("access_token").string(), getProfile(abstractObject.get("access_token").string()), new HTTPClient("https://api.github.com").authorization("token", abstractObject.get("access_token").string())); - } - - return null; - } - - - public Object redirect(Exchange exchange, OAuth2Module oAuth2Module) { - try { - exchange.redirect("https://github.com/login/oauth/authorize?client_id="+clientId+"&scope="+ URLEncoder.encode(String.join(" ", scopes), "UTF-8")+"&redirect_uri="+URLEncoder.encode(redirectDomain+oAuth2Module.getPathPrefix()+getName()+"/callback", "UTF-8")); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - return null; - } - return ""; - } - - @Override - public Profile getProfile(String accessToken) { - Profile profile = new Profile(); - AbstractObject userData = get("/user") - .header("Authorization", "token "+accessToken) - .data().object(); - - if (userData.has("id")) - profile.id = userData.get("id").number().toString(); - if (userData.has("name")) - profile.name = userData.get("name").string(); - if (userData.has("avatar_url")) - profile.avatar = userData.get("avatar_url").string(); - - userData.forEach(profile::set); - - get("/user/emails") - .authorization("token", accessToken) - .data().array().forEach(abstractElement -> { - if (profile.mail == null) { - profile.mail = abstractElement.object().get("email").string(); - } - }); - return profile; - } - - -} diff --git a/src/main/java/org/javawebstack/passport/services/oauth2/GoogleOAuth2Service.java b/src/main/java/org/javawebstack/passport/services/oauth2/GoogleOAuth2Service.java deleted file mode 100644 index c02b82c..0000000 --- a/src/main/java/org/javawebstack/passport/services/oauth2/GoogleOAuth2Service.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.javawebstack.passport.services.oauth2; - -import com.google.api.client.auth.oauth2.Credential; -import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; -import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse; -import com.google.api.client.http.javanet.NetHttpTransport; -import com.google.api.client.json.gson.GsonFactory; -import com.google.api.services.oauth2.Oauth2; -import com.google.api.services.oauth2.model.Userinfo; -import org.javawebstack.httpclient.HTTPClient; -import org.javawebstack.httpserver.Exchange; -import org.javawebstack.passport.OAuth2Module; -import org.javawebstack.passport.Profile; - -import java.io.IOException; -import java.util.Arrays; - - -public class GoogleOAuth2Service extends HTTPClient implements OAuth2Service { - private final String clientId; - private final String secret; - private String redirectDomain; - private GoogleAuthorizationCodeFlow googleAuthorizationCodeFlow; - - public GoogleOAuth2Service(String clientId, String secret, String redirectDomain){ - setBaseUrl("https://api.github.com"); - this.clientId = clientId; - this.secret = secret; - this.redirectDomain = redirectDomain; - googleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow.Builder( - new NetHttpTransport(), - GsonFactory.getDefaultInstance(), - clientId, - secret, - Arrays.asList( - "https://www.googleapis.com/auth/userinfo.profile", - "https://www.googleapis.com/auth/userinfo.email" - ) - ).build(); - } - - public String getName() { - return "google"; - } - - public GoogleOAuth2Service setScopes(String[] scopes) { - googleAuthorizationCodeFlow.getScopes().clear(); - for (String scope : scopes) { - googleAuthorizationCodeFlow.getScopes().add(scope); - } - return this; - } - - public OAuth2Callback callback(Exchange exchange, OAuth2Module oAuth2Module) { - GoogleTokenResponse code = null; - try { - code = googleAuthorizationCodeFlow.newTokenRequest(exchange.rawRequest().getParameter("code")) - .setRedirectUri(redirectDomain+oAuth2Module.getPathPrefix()+getName()+"/callback") - .execute(); - } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException(); - } - Profile profile = getProfile(code.getAccessToken()); - if (profile != null) - return new OAuth2Callback(code.getAccessToken(), profile, new HTTPClient("https://www.googleapis.com/oauth2/v1").bearer(code.getAccessToken())).setRefreshToken(code.getRefreshToken()); - - return null; - } - - - public Object redirect(Exchange exchange, OAuth2Module oAuth2Module) { - exchange.redirect( - googleAuthorizationCodeFlow - .newAuthorizationUrl() - .setAccessType("offline") - .setRedirectUri(redirectDomain+oAuth2Module.getPathPrefix()+getName()+"/callback") - .build() - ); - return ""; - } - - @Override - public Profile getProfile(String accessToken) { - Credential credential = new GoogleCredential.Builder() - .setTransport(new NetHttpTransport()) - .setJsonFactory(GsonFactory.getDefaultInstance()) - .setClientSecrets(clientId, secret) - .build().setAccessToken(accessToken); - - Oauth2 oauth2 = new Oauth2.Builder( - new NetHttpTransport(), - GsonFactory.getDefaultInstance(), - credential - ).setApplicationName(clientId).build(); - try { - Profile profile = new Profile(); - Userinfo userinfo = oauth2.userinfo().get().execute(); - profile.id = userinfo.getId(); - profile.name = userinfo.getName(); - profile.avatar = userinfo.getPicture(); - profile.mail = userinfo.getEmail(); - userinfo.forEach((key, val)->{ - profile.set(key, val.toString()); - }); - return profile; - } catch (IOException e) { - e.printStackTrace(); - } - - return null; - } - - public GoogleAuthorizationCodeFlow getGoogleAuthorizationCodeFlow() { - return googleAuthorizationCodeFlow; - } - - public String refreshToken(String refreshToken){ - return googleAuthorizationCodeFlow.newTokenRequest(refreshToken).getCode(); - } -} diff --git a/src/main/java/org/javawebstack/passport/services/oauth2/InteraAppsOAuth2Service.java b/src/main/java/org/javawebstack/passport/services/oauth2/InteraAppsOAuth2Service.java deleted file mode 100644 index be00a6a..0000000 --- a/src/main/java/org/javawebstack/passport/services/oauth2/InteraAppsOAuth2Service.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.javawebstack.passport.services.oauth2; - -import org.javawebstack.abstractdata.AbstractElement; -import org.javawebstack.abstractdata.AbstractObject; -import org.javawebstack.httpclient.HTTPClient; -import org.javawebstack.httpserver.Exchange; -import org.javawebstack.passport.OAuth2Module; -import org.javawebstack.passport.Profile; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Arrays; -import java.util.List; - -public class InteraAppsOAuth2Service extends HTTPClient implements OAuth2Service { - private final String clientId; - private final String secret; - private String[] scopes; - private String redirectDomain; - - public InteraAppsOAuth2Service(String clientId, String secret, String redirectDomain){ - this.redirectDomain = redirectDomain; - setBaseUrl("https://accounts.interaapps.de/api/v2"); - this.clientId = clientId; - this.secret = secret; - scopes = new String[]{"user:read"}; - } - - public InteraAppsOAuth2Service setScopes(String[] scopes) { - this.scopes = scopes; - return this; - } - - public OAuth2Callback callback(Exchange exchange, OAuth2Module oAuth2Module){ - AbstractObject data = post("/authorization/oauth2/access_token") - .jsonBodyElement(new AbstractObject() - .set("client_id", clientId) - .set("client_secret", secret) - .set("code", exchange.rawRequest().getParameter("code")) - ) - .data() - .object(); - - List scopes = Arrays.asList(this.scopes); - - for (AbstractElement scope : data.get("scope_list").array()) { - if (!scopes.contains(scope.string())) - return null; - } - - if (data.get("success").bool()) { - - String accessToken = data.get("access_token").string(); - - - return new OAuth2Callback(accessToken, getProfile(accessToken), new HTTPClient("https://accounts.interaapps.de/").bearer(accessToken)).setRefreshToken(data.get("refresh_token").string()); - } - - return null; - } - - public Object redirect(Exchange exchange, OAuth2Module oAuth2Module) { - try { - exchange.redirect("https://accounts.interaapps.de/auth/oauth2?client_id="+clientId+"&scope="+URLEncoder.encode(String.join(" ", scopes), "UTF-8")+"&redirect_uri="+URLEncoder.encode(redirectDomain+oAuth2Module.getPathPrefix()+getName()+"/callback", "UTF-8")); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - return null; - } - return ""; - } - - public Profile getProfile(String accessToken) { - AbstractObject userData = get("/user") - .bearer(accessToken) - .data().object(); - Profile profile = new Profile(); - - if (userData.has("id")) - profile.id = userData.get("id").number().toString(); - if (userData.has("name")) - profile.name = userData.get("name").string(); - if (userData.has("mail")) - profile.mail = userData.get("mail").string(); - if (userData.has("profile_picture")) - profile.avatar = userData.get("profile_picture").string(); - - userData.forEach(profile::set); - return profile; - } - - - public String getName() { - return "interaapps"; - } - - public String refreshToken(String refreshToken){ - return post("/authorization/oauth2/access_token") - .jsonBodyElement(new AbstractObject() - .set("client_id", clientId) - .set("client_secret", secret) - .set("code", refreshToken) - ) - .data() - .object().get("access_token").string(); - } - -} diff --git a/src/main/java/org/javawebstack/passport/services/oauth2/OAuth2Callback.java b/src/main/java/org/javawebstack/passport/services/oauth2/OAuth2Callback.java deleted file mode 100644 index b935986..0000000 --- a/src/main/java/org/javawebstack/passport/services/oauth2/OAuth2Callback.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.javawebstack.passport.services.oauth2; - -import org.javawebstack.httpclient.HTTPClient; -import org.javawebstack.passport.Profile; - -public class OAuth2Callback { - private String token; - private Profile profile; - private HTTPClient httpClient; - private String refreshToken; - - public OAuth2Callback(String token, Profile profile, HTTPClient httpClient) { - this.token = token; - this.profile = profile; - this.httpClient = httpClient; - } - - public Profile getProfile() { - return profile; - } - - public String getToken() { - return token; - } - - public HTTPClient getHttpClient() { - return httpClient; - } - - public OAuth2Callback setRefreshToken(String refreshToken) { - this.refreshToken = refreshToken; - return this; - } - - public String getRefreshToken() { - return refreshToken; - } - -} diff --git a/src/main/java/org/javawebstack/passport/services/oauth2/OAuth2CallbackHandler.java b/src/main/java/org/javawebstack/passport/services/oauth2/OAuth2CallbackHandler.java deleted file mode 100644 index b5b0aa8..0000000 --- a/src/main/java/org/javawebstack/passport/services/oauth2/OAuth2CallbackHandler.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.javawebstack.passport.services.oauth2; - -import org.javawebstack.httpclient.HTTPClient; -import org.javawebstack.httpserver.Exchange; - -public interface OAuth2CallbackHandler { - Object callback(String service, Exchange exchange, OAuth2Callback callback); -} diff --git a/src/main/java/org/javawebstack/passport/services/oauth2/OAuth2Service.java b/src/main/java/org/javawebstack/passport/services/oauth2/OAuth2Service.java deleted file mode 100644 index c3b2d87..0000000 --- a/src/main/java/org/javawebstack/passport/services/oauth2/OAuth2Service.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.javawebstack.passport.services.oauth2; - -import org.javawebstack.httpserver.Exchange; -import org.javawebstack.passport.AuthService; -import org.javawebstack.passport.OAuth2Module; -import org.javawebstack.passport.Profile; - -public interface OAuth2Service extends AuthService { - OAuth2Callback callback(Exchange exchange, OAuth2Module oAuth2Module); - - Object redirect(Exchange exchange, OAuth2Module oAuth2Module); - - Profile getProfile(String accessToken); -} diff --git a/src/main/java/org/javawebstack/passport/services/oauth2/TwitchOAuth2Service.java b/src/main/java/org/javawebstack/passport/services/oauth2/TwitchOAuth2Service.java deleted file mode 100644 index d39fd08..0000000 --- a/src/main/java/org/javawebstack/passport/services/oauth2/TwitchOAuth2Service.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.javawebstack.passport.services.oauth2; - -import org.javawebstack.abstractdata.AbstractObject; -import org.javawebstack.abstractdata.util.QueryString; -import org.javawebstack.httpclient.HTTPClient; -import org.javawebstack.httpserver.Exchange; -import org.javawebstack.httpserver.helper.MimeType; -import org.javawebstack.passport.OAuth2Module; -import org.javawebstack.passport.Profile; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; - -public class TwitchOAuth2Service extends HTTPClient implements OAuth2Service { - private final String clientId; - private final String clientSecret; - private String[] scopes = new String[]{"user:read:email"}; // reference: https://dev.twitch.tv/docs/authentication/#scopes - private final String redirectDomain; - private boolean forceVerify = false; - private String state; - private OAuth2Module oAuth2Module; - - public TwitchOAuth2Service(String clientId, String clientSecret, String redirectDomain, OAuth2Module oAuth2Module) { - this.oAuth2Module = oAuth2Module; - setBaseUrl("https://api.twitch.tv/helix"); - this.clientId = clientId; - this.redirectDomain = redirectDomain; - this.clientSecret = clientSecret; - } - - public TwitchOAuth2Service setState(String state) { - this.state = state; - return this; - } - - public String getState() { - return state; - } - - public TwitchOAuth2Service setScopes(String[] scopes) { - this.scopes = scopes; - return this; - } - - public TwitchOAuth2Service setForceVerify(boolean forceVerify) { - this.forceVerify = forceVerify; - return this; - } - - public boolean isForceVerify() { - return forceVerify; - } - - public String getName() { - return "twitch"; - } - - public OAuth2Callback callback(Exchange exchange, OAuth2Module oAuth2Module) { - AbstractObject abstractObject = new HTTPClient("https://id.twitch.tv").post("/oauth2/token") - .formBodyString(new QueryString() - .set("client_id", clientId) - .set("client_secret", clientSecret) - .set("code", exchange.rawRequest().getParameter("code")) - .set("grant_type", "authorization_code") - .set("redirect_uri", createRedirectUrl(oAuth2Module.getPathPrefix())) - .toString()) - .header("Accept", MimeType.JSON.getMimeTypes().get(0)) - .data().object(); - - if (abstractObject.has("scope")) { - String accessToken = abstractObject.get("access_token").string(); - return new OAuth2Callback(accessToken, getProfile(accessToken), new HTTPClient("https://api.twitch.tv/helix").bearer(accessToken)); - } - - return null; - } - - public Object redirect(Exchange exchange, OAuth2Module oAuth2Module) { - try { - exchange.redirect("https://id.twitch.tv/oauth2/authorize?client_id="+clientId+"&response_type=code&scope="+ URLEncoder.encode(String.join(" ", scopes), "UTF-8")+"&redirect_uri="+URLEncoder.encode(createRedirectUrl(oAuth2Module.getPathPrefix()), "UTF-8")+"&force_verify="+forceVerify+"&state="+state); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - return null; - } - return ""; - } - - private String createRedirectUrl(String redirectPathPrefix){ - return redirectDomain +redirectPathPrefix+getName()+"/callback"; - } - - public Profile getProfile(String accessToken) { - Profile profile = new Profile(); - - AbstractObject userData = get("/users") - .bearer(accessToken) - .header("Client-Id", clientId) - .data().object().get("data").array().get(0).object(); - - if (userData.has("id")) - profile.id = userData.get("id").string(); - if (userData.has("login")) - profile.name = userData.get("login").string(); - if (userData.has("profile_image_url")) - profile.avatar = userData.get("profile_image_url").string(); - if (userData.has("email")) - profile.mail = userData.get("email").string(); - userData.forEach(profile::set); - - return profile; - } -} diff --git a/src/main/java/org/javawebstack/passport/strategies/Strategy.java b/src/main/java/org/javawebstack/passport/strategies/Strategy.java new file mode 100644 index 0000000..c6ecf03 --- /dev/null +++ b/src/main/java/org/javawebstack/passport/strategies/Strategy.java @@ -0,0 +1,23 @@ +package org.javawebstack.passport.strategies; + +import org.javawebstack.httpserver.HTTPServer; +import org.javawebstack.passport.Passport; + +public abstract class Strategy { + protected String name; + protected Passport passport; + + public abstract void createRoutes(String prefixUrl, HTTPServer httpServer); + + public String getName() { + return name; + } + + public Passport getPassport() { + return passport; + } + + public void setPassport(Passport passport) { + this.passport = passport; + } +} diff --git a/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Callback.java b/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Callback.java new file mode 100644 index 0000000..438f660 --- /dev/null +++ b/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Callback.java @@ -0,0 +1,25 @@ +package org.javawebstack.passport.strategies.oauth2; + +import org.javawebstack.httpclient.HTTPClient; + +public abstract class OAuth2Callback { + protected String accessToken; + protected String refreshToken; + + public OAuth2Callback(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + + public abstract HTTPClient createApiClient(); + + public abstract OAuth2Profile getProfile(); + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } +} diff --git a/src/main/java/org/javawebstack/passport/Profile.java b/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Profile.java similarity index 78% rename from src/main/java/org/javawebstack/passport/Profile.java rename to src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Profile.java index ac8ca68..5a480d5 100644 --- a/src/main/java/org/javawebstack/passport/Profile.java +++ b/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Profile.java @@ -1,8 +1,8 @@ -package org.javawebstack.passport; +package org.javawebstack.passport.strategies.oauth2; import org.javawebstack.abstractdata.AbstractObject; -public class Profile extends AbstractObject { +public class OAuth2Profile extends AbstractObject { public String id; public String name; public String mail; diff --git a/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Provider.java b/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Provider.java new file mode 100644 index 0000000..379ccff --- /dev/null +++ b/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Provider.java @@ -0,0 +1,9 @@ +package org.javawebstack.passport.strategies.oauth2; + +import org.javawebstack.abstractdata.AbstractObject; + +public abstract class OAuth2Provider { + public abstract OAuth2Callback callback(AbstractObject queryParameters, String callbackUrl); + + public abstract String redirect(String callbackUrl); +} diff --git a/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Strategy.java b/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Strategy.java new file mode 100644 index 0000000..4726ed2 --- /dev/null +++ b/src/main/java/org/javawebstack/passport/strategies/oauth2/OAuth2Strategy.java @@ -0,0 +1,51 @@ +package org.javawebstack.passport.strategies.oauth2; + +import org.javawebstack.httpserver.Exchange; +import org.javawebstack.httpserver.HTTPServer; +import org.javawebstack.passport.strategies.Strategy; + +import java.util.HashMap; +import java.util.Map; + +public class OAuth2Strategy extends Strategy { + private Map providers = new HashMap<>(); + private HttpCallbackHandler httpCallbackHandler; + private String host; + + + public OAuth2Strategy(String host){ + + this.host = host; + } + + public OAuth2Strategy use(String name, OAuth2Provider oAuth2Provider){ + providers.put(name, oAuth2Provider); + return this; + } + + public void createRoutes(String prefixUrl, HTTPServer httpServer) { + providers.forEach((name, oauth2) -> { + final String callbackUrl = prefixUrl+"/"+name+"/callback"; + httpServer.get(prefixUrl+"/"+name, e -> { + e.redirect(oauth2.redirect(host+callbackUrl)); + return ""; + }); + + httpServer.get(callbackUrl, e -> { + return httpCallbackHandler.handle(e, oauth2.callback(e.getQueryParameters(), host+callbackUrl)); + }); + }); + } + + public Map getProviders() { + return providers; + } + + public void setHttpCallbackHandler(HttpCallbackHandler httpCallbackHandler) { + this.httpCallbackHandler = httpCallbackHandler; + } + + public interface HttpCallbackHandler { + Object handle(Exchange exchange, OAuth2Callback callback); + } +} diff --git a/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/DiscordOAuth2Provider.java b/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/DiscordOAuth2Provider.java new file mode 100644 index 0000000..a7bed3e --- /dev/null +++ b/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/DiscordOAuth2Provider.java @@ -0,0 +1,90 @@ +package org.javawebstack.passport.strategies.oauth2.providers; + +import org.javawebstack.abstractdata.AbstractObject; +import org.javawebstack.abstractdata.util.QueryString; +import org.javawebstack.httpclient.HTTPClient; +import org.javawebstack.passport.strategies.oauth2.OAuth2Callback; +import org.javawebstack.passport.strategies.oauth2.OAuth2Profile; +import org.javawebstack.passport.strategies.oauth2.OAuth2Provider; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +public class DiscordOAuth2Provider extends OAuth2Provider { + private String clientId; + private String secret; + private String[] scopes = {"email","identify"}; + private HTTPClient discordClient; + + public DiscordOAuth2Provider(String clientId, String secret){ + this.clientId = clientId; + this.secret = secret; + discordClient = new HTTPClient("https://discord.com"); + } + + public DiscordOAuth2Provider setScopes(String... scopes) { + this.scopes = scopes; + return this; + } + + public OAuth2Callback callback(AbstractObject queryParameters, String callbackUrl) { + AbstractObject abstractObject = discordClient.post("/api/oauth2/token") + .header("User-Agent", "JWSPassportClient/1") + .formBodyString(new QueryString() + .set("client_id", clientId) + .set("client_secret", secret) + .set("code", queryParameters.string("code")) + .set("grant_type", "authorization_code") + .set("redirect_uri", callbackUrl) + .set("scope", String.join(" ", scopes)) + .toString() + ) + .data().object(); + return new OAuth2Callback(abstractObject.string("access_token"), abstractObject.string("refresh_token")); + } + + public String redirect(String callbackUrl) { + try { + return "https://discord.com/api/oauth2/authorize?response_type=code&client_id="+clientId+"&prompt=consent&scope="+ URLEncoder.encode(String.join(" ", scopes), "UTF-8")+"&redirect_uri="+URLEncoder.encode(callbackUrl, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return ""; + } + + public static class OAuth2Callback extends org.javawebstack.passport.strategies.oauth2.OAuth2Callback { + public OAuth2Callback(String accessToken, String refreshToken) { + super(accessToken, refreshToken); + } + + public HTTPClient createApiClient() { + return new HTTPClient("https://discord.com/").bearer(accessToken); + } + + public OAuth2Profile getProfile() { + OAuth2Profile profile = new OAuth2Profile(); + + AbstractObject data = createApiClient().get("/api/users/@me") + .bearer(accessToken) + .header("User-Agent", "JWSPassportClient/1") + .data().object(); + + if (data.has("id")) { + profile.id = data.get("id").string(); + + if (data.has("avatar")) + profile.avatar = "https://cdn.discordapp.com/avatars/"+profile.id+"/"+data.get("avatar").string()+".png"; + } + + if (data.has("username")) + profile.name = data.get("username").string(); + + if (data.has("email")) + profile.mail = data.get("email").string(); + + data.forEach(profile::set); + + return profile; + } + } +} diff --git a/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/GitHubOAuth2Provider.java b/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/GitHubOAuth2Provider.java new file mode 100644 index 0000000..5be7639 --- /dev/null +++ b/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/GitHubOAuth2Provider.java @@ -0,0 +1,94 @@ +package org.javawebstack.passport.strategies.oauth2.providers; + +import org.javawebstack.abstractdata.AbstractElement; +import org.javawebstack.abstractdata.AbstractObject; +import org.javawebstack.abstractdata.util.QueryString; +import org.javawebstack.httpclient.HTTPClient; +import org.javawebstack.httpserver.helper.MimeType; +import org.javawebstack.passport.strategies.oauth2.OAuth2Profile; +import org.javawebstack.passport.strategies.oauth2.OAuth2Provider; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.List; + +public class GitHubOAuth2Provider extends OAuth2Provider { + private String clientId; + private String secret; + private String[] scopes = {"read:user","user:email"}; + public GitHubOAuth2Provider(String clientId, String secret){ + this.clientId = clientId; + this.secret = secret; + } + + public GitHubOAuth2Provider setScopes(String... scopes) { + this.scopes = scopes; + return this; + } + + public OAuth2Callback callback(AbstractObject queryParameters, String callbackUrl) { + AbstractObject abstractObject = new HTTPClient("https://github.com").post("/login/oauth/access_token") + .formBodyString(new QueryString() + .set("client_id", clientId) + .set("client_secret", secret) + .set("code", queryParameters.string("code")) + .toString()) + .header("Accept", MimeType.JSON.getMimeTypes().get(0)) + .data().object(); + + + if (abstractObject.has("scope")/* && abstractObject.get("scope").string().equals("read:user,user:email")*/) { + System.out.println(abstractObject.toJsonString()); + return new OAuth2Callback(abstractObject.string("access_token"), abstractObject.has("refresh_token") ? abstractObject.string("refresh_token") : null); + } + + return null; + } + + public String redirect(String callbackUrl) { + try { + return "https://github.com/login/oauth/authorize?client_id="+clientId+"&scope="+ URLEncoder.encode(String.join(" ", scopes), "UTF-8")+"&redirect_uri="+URLEncoder.encode(callbackUrl, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return ""; + } + + public static class OAuth2Callback extends org.javawebstack.passport.strategies.oauth2.OAuth2Callback { + public OAuth2Callback(String accessToken, String refreshToken) { + super(accessToken, refreshToken); + } + + public HTTPClient createApiClient() { + return new HTTPClient("https://api.github.com").authorization("token", accessToken); + } + + public OAuth2Profile getProfile() { + HTTPClient apiClient = createApiClient(); + OAuth2Profile profile = new OAuth2Profile(); + + AbstractObject userData = apiClient.get("/user") + .header("Authorization", "token "+accessToken) + .data().object(); + + if (userData.has("id")) + profile.id = userData.get("id").number().toString(); + if (userData.has("name")) + profile.name = userData.get("name").string(); + if (userData.has("avatar_url")) + profile.avatar = userData.get("avatar_url").string(); + + userData.forEach(profile::set); + + apiClient.get("/user/emails") + .authorization("token", accessToken) + .data().array().forEach(abstractElement -> { + if (profile.mail == null) { + profile.mail = abstractElement.object().get("email").string(); + } + }); + return profile; + } + } +} diff --git a/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/GoogleOAuth2Provider.java b/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/GoogleOAuth2Provider.java new file mode 100644 index 0000000..6ae9e5a --- /dev/null +++ b/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/GoogleOAuth2Provider.java @@ -0,0 +1,113 @@ +package org.javawebstack.passport.strategies.oauth2.providers; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.services.oauth2.Oauth2; +import com.google.api.services.oauth2.model.Userinfo; +import org.javawebstack.abstractdata.AbstractObject; +import org.javawebstack.httpclient.HTTPClient; +import org.javawebstack.passport.strategies.oauth2.OAuth2Callback; +import org.javawebstack.passport.strategies.oauth2.OAuth2Profile; +import org.javawebstack.passport.strategies.oauth2.OAuth2Provider; + +import java.io.IOException; +import java.util.Arrays; + +public class GoogleOAuth2Provider extends OAuth2Provider { + private String clientId; + private String secret; + private GoogleAuthorizationCodeFlow googleAuthorizationCodeFlow; + + public GoogleOAuth2Provider(String clientId, String secret){ + this.clientId = clientId; + this.secret = secret; + + googleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow.Builder( + new NetHttpTransport(), + GsonFactory.getDefaultInstance(), + clientId, + secret, + Arrays.asList( + "https://www.googleapis.com/auth/userinfo.profile", + "https://www.googleapis.com/auth/userinfo.email" + ) + ).build(); + } + + public GoogleOAuth2Provider setScopes(String[] scopes) { + googleAuthorizationCodeFlow.getScopes().clear(); + for (String scope : scopes) { + googleAuthorizationCodeFlow.getScopes().add(scope); + } + return this; + } + + public OAuth2Callback callback(AbstractObject queryParameters, String callbackUrl) { + try { + GoogleTokenResponse code =googleAuthorizationCodeFlow.newTokenRequest(queryParameters.string("code")) + .setRedirectUri(callbackUrl) + .execute(); + return new OAuth2Callback(code.getAccessToken(), code.getRefreshToken(), clientId, secret); + } catch (IOException e) { + return null; + } + } + + @Override + public String redirect(String callbackUrl) { + return googleAuthorizationCodeFlow + .newAuthorizationUrl() + .setAccessType("offline") + .setRedirectUri(callbackUrl) + .build(); + } + + public static class OAuth2Callback extends org.javawebstack.passport.strategies.oauth2.OAuth2Callback { + private String clientId; + private String secret; + + public OAuth2Callback(String accessToken, String refreshToken, String clientId, String secret) { + super(accessToken, refreshToken); + this.clientId = clientId; + this.secret = secret; + } + + public HTTPClient createApiClient() { + return new HTTPClient("https://www.googleapis.com/oauth2/v1").bearer(accessToken); + } + + public OAuth2Profile getProfile() { + Credential credential = new GoogleCredential.Builder() + .setTransport(new NetHttpTransport()) + .setJsonFactory(GsonFactory.getDefaultInstance()) + .setClientSecrets(clientId, secret) + .build().setAccessToken(accessToken); + + Oauth2 oauth2 = new Oauth2.Builder( + new NetHttpTransport(), + GsonFactory.getDefaultInstance(), + credential + ).setApplicationName(clientId).build(); + try { + OAuth2Profile profile = new OAuth2Profile(); + Userinfo userinfo = oauth2.userinfo().get().execute(); + profile.id = userinfo.getId(); + profile.name = userinfo.getName(); + profile.avatar = userinfo.getPicture(); + profile.mail = userinfo.getEmail(); + userinfo.forEach((key, val)->{ + profile.set(key, val.toString()); + }); + return profile; + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + } +} diff --git a/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/InteraAppsOAuth2Provider.java b/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/InteraAppsOAuth2Provider.java new file mode 100644 index 0000000..0544afc --- /dev/null +++ b/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/InteraAppsOAuth2Provider.java @@ -0,0 +1,95 @@ +package org.javawebstack.passport.strategies.oauth2.providers; + +import org.javawebstack.abstractdata.AbstractElement; +import org.javawebstack.abstractdata.AbstractObject; +import org.javawebstack.httpclient.HTTPClient; +import org.javawebstack.httpclient.HTTPRequest; +import org.javawebstack.passport.strategies.oauth2.OAuth2Callback; +import org.javawebstack.passport.strategies.oauth2.OAuth2Profile; +import org.javawebstack.passport.strategies.oauth2.OAuth2Provider; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.List; + +public class InteraAppsOAuth2Provider extends OAuth2Provider { + private String clientId; + private String secret; + private String[] scopes = {"user:read"}; + private HTTPClient interaAppsAccountsClient; + + public InteraAppsOAuth2Provider(String clientId, String secret){ + this.clientId = clientId; + this.secret = secret; + interaAppsAccountsClient = new HTTPClient("https://accounts.interaapps.de/api/v2"); + } + + public InteraAppsOAuth2Provider setScopes(String... scopes) { + this.scopes = scopes; + return this; + } + + public OAuth2Callback callback(AbstractObject queryParameters, String callbackUrl) { + AbstractObject data = interaAppsAccountsClient.post("/authorization/oauth2/access_token") + .jsonBodyElement(new AbstractObject() + .set("client_id", clientId) + .set("client_secret", secret) + .set("code", queryParameters.string("code")) + ) + .data() + .object(); + + List scopes = Arrays.asList(this.scopes); + + if (!data.bool("success")) + return null; + + for (AbstractElement scope : data.get("scope_list").array()) { + if (!scopes.contains(scope.string())) + return null; + } + + if (data.get("success").bool()) { + return new OAuth2Callback(data.string("access_token"), data.string("refresh_token")); + } + + return null; + } + + public String redirect(String callbackUrl) { + try { + return "https://accounts.interaapps.de/auth/oauth2?client_id="+clientId+"&scope="+ URLEncoder.encode(String.join(" ", scopes), "UTF-8")+"&redirect_uri="+URLEncoder.encode(callbackUrl, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return ""; + } + + public static class OAuth2Callback extends org.javawebstack.passport.strategies.oauth2.OAuth2Callback { + public OAuth2Callback(String accessToken, String refreshToken) { + super(accessToken, refreshToken); + } + + public HTTPClient createApiClient() { + return new HTTPClient("https://accounts.interaapps.de/api/v2").bearer(accessToken); + } + + public OAuth2Profile getProfile() { + AbstractObject userData = createApiClient().get("/user").data().object(); + OAuth2Profile profile = new OAuth2Profile(); + + if (userData.has("id")) + profile.id = userData.get("id").number().toString(); + if (userData.has("name")) + profile.name = userData.get("name").string(); + if (userData.has("mail")) + profile.mail = userData.get("mail").string(); + if (userData.has("profile_picture")) + profile.avatar = userData.get("profile_picture").string(); + + userData.forEach(profile::set); + return profile; + } + } +} diff --git a/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/TwitchOAuth2Provider.java b/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/TwitchOAuth2Provider.java new file mode 100644 index 0000000..5cf327a --- /dev/null +++ b/src/main/java/org/javawebstack/passport/strategies/oauth2/providers/TwitchOAuth2Provider.java @@ -0,0 +1,109 @@ +package org.javawebstack.passport.strategies.oauth2.providers; + +import org.javawebstack.abstractdata.AbstractObject; +import org.javawebstack.abstractdata.util.QueryString; +import org.javawebstack.httpclient.HTTPClient; +import org.javawebstack.httpserver.helper.MimeType; +import org.javawebstack.passport.strategies.oauth2.OAuth2Profile; +import org.javawebstack.passport.strategies.oauth2.OAuth2Provider; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +public class TwitchOAuth2Provider extends OAuth2Provider { + private String clientId; + private String secret; + private String[] scopes = {"user:read:email"}; // reference: https://dev.twitch.tv/docs/authentication/#scopes + + private boolean forceVerify = false; + private String state; + + public TwitchOAuth2Provider(String clientId, String secret){ + this.clientId = clientId; + this.secret = secret; + } + + public TwitchOAuth2Provider setScopes(String... scopes) { + this.scopes = scopes; + return this; + } + + public OAuth2Callback callback(AbstractObject queryParameters, String callbackUrl) { + AbstractObject abstractObject = new HTTPClient("https://id.twitch.tv").post("/oauth2/token") + .formBodyString(new QueryString() + .set("client_id", clientId) + .set("client_secret", secret) + .set("code", queryParameters.string("code")) + .set("grant_type", "authorization_code") + .set("redirect_uri", callbackUrl) + .toString()) + .header("Accept", MimeType.JSON.getMimeTypes().get(0)) + .data().object(); + + if (abstractObject.has("scope")) { + return new OAuth2Callback(abstractObject.string("access_token"), abstractObject.string("refresh_token"), clientId); + } + + return null; + } + + public TwitchOAuth2Provider setState(String state) { + this.state = state; + return this; + } + + public String getState() { + return state; + } + + public TwitchOAuth2Provider setForceVerify(boolean forceVerify) { + this.forceVerify = forceVerify; + return this; + } + + public boolean isForceVerify() { + return forceVerify; + } + + public String redirect(String callbackUrl) { + try { + return "https://id.twitch.tv/oauth2/authorize?client_id="+clientId+"&response_type=code&scope="+ URLEncoder.encode(String.join(" ", scopes), "UTF-8")+"&redirect_uri="+URLEncoder.encode(callbackUrl, "UTF-8")+"&force_verify="+forceVerify+"&state="+state; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return ""; + } + + public static class OAuth2Callback extends org.javawebstack.passport.strategies.oauth2.OAuth2Callback { + private String clientId; + + public OAuth2Callback(String accessToken, String refreshToken, String clientId) { + super(accessToken, refreshToken); + this.clientId = clientId; + } + + public HTTPClient createApiClient() { + return new HTTPClient("https://api.twitch.tv/helix").bearer(accessToken); + } + + public OAuth2Profile getProfile() { + OAuth2Profile profile = new OAuth2Profile(); + + AbstractObject userData = createApiClient().get("/users") + .header("Client-Id", clientId) + .data().object().get("data").array().get(0).object(); + + if (userData.has("id")) + profile.id = userData.get("id").string(); + if (userData.has("login")) + profile.name = userData.get("login").string(); + if (userData.has("profile_image_url")) + profile.avatar = userData.get("profile_image_url").string(); + if (userData.has("email")) + profile.mail = userData.get("email").string(); + userData.forEach(profile::set); + + return profile; + } + } +}