From a2abc73a83852f6e0ad26d2edd3ffb99aec79181 Mon Sep 17 00:00:00 2001 From: wapeety Date: Sun, 12 Nov 2023 18:24:24 +0100 Subject: [PATCH 01/12] Throws stuff... We're having difficulties --- app/src/main/java/models/API/API.java | 129 ++++++++++++++++---------- 1 file changed, 79 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/models/API/API.java b/app/src/main/java/models/API/API.java index 808cb30..6a9e433 100644 --- a/app/src/main/java/models/API/API.java +++ b/app/src/main/java/models/API/API.java @@ -1,13 +1,8 @@ package models.API; +import android.graphics.Picture; import android.util.Log; -import io.jsonwebtoken.Jwt; -import io.jsonwebtoken.JwtParser; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.io.Parser; -import io.jsonwebtoken.security.Jwk; -import io.jsonwebtoken.security.Jwks; import org.conscrypt.BuildConfig; import org.jetbrains.annotations.NotNull; import org.json.JSONException; @@ -15,8 +10,18 @@ import java.io.IOException; import java.security.PublicKey; +import java.time.Instant; +import java.util.Date; +import java.util.Map; import java.util.concurrent.CompletableFuture; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.Jwks; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Credentials; @@ -27,12 +32,13 @@ public class API { private static API instance; private OkHttpClient client; - - private Jwt jwt; - private Jwk jwk; + private JwtParser jwtParser; + private Jws jws; + private Jwk jwk; private boolean isLogged = false; public static final String BASE_URL = "https://esse3.unive.it/e3rest/api/"; + private API() { this.client = new OkHttpClient.Builder().build(); } @@ -41,7 +47,6 @@ public static API getInstance(){ return instance == null ? instance = new API() : instance; } - @NotNull public static CompletableFuture saveJwk() { Request request = new Request.Builder() @@ -58,7 +63,10 @@ public void onFailure(@NotNull Call call, @NotNull IOException e) { @Override public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { Parser> parser = Jwks.parser().build(); - API.getInstance().jwk = parser.parse(response.body().string()); + API.getInstance().jwk = (Jwk) parser.parse(response.body().string()); + API.getInstance().jwtParser = Jwts.parser() + .verifyWith(API.getInstance().jwk.toKey()) + .build(); future.complete(true); } }); @@ -81,50 +89,31 @@ public static CompletableFuture login (String username, String password final CompletableFuture future = new CompletableFuture<>(); API.getInstance().client.newCall(request).enqueue(new Callback() { + @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { - throw new RuntimeException("Could not perform login: " + (BuildConfig.DEBUG ? e.getMessage(): "Unknown error")); + throw new RuntimeException("Could not perform login: " + (BuildConfig.DEBUG ? e.getMessage() : "Unknown error")); } @Override public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - if(response.code() == 200){ + if (response.code() == 200) { try { + if (!saveJwk().join()) + throw new RuntimeException("Failed to save JWK"); + + //log data to console Log.i("API_TAG", response.toString()); JSONObject json = new JSONObject(response.body().string()); - boolean hasJwk = false; - if (API.getInstance().jwk == null) { - hasJwk = API.saveJwk().join(); - } - - if (!hasJwk) - throw new RuntimeException("Diocane"); - JwtParser parser = Jwts.parser() - .verifyWith(API.getInstance().jwk.toKey()) - .build(); - - - - try { - Jwt j = parser.parse(json.getString("jwt")); - - // API.getInstance().jwt = json.getString("jwt"); - API.getInstance().jwt = j; - } catch (Exception e) { - Log.w("TAG_API", "JWT not parsed", e); - } - - - + API.getInstance().jws = API.getInstance().jwtParser.parseSignedClaims(json.getString("jwt")); // Meglio mettere qui isLogged in modo che se si lancia un'eccezione resta false API.getInstance().isLogged = true; - } - catch (JSONException e){ + } catch (JSONException e) { API.getInstance().isLogged = false; Log.w("TAG_API", (BuildConfig.DEBUG ? "Could not parse response: " + e.getMessage() : "Unknown error")); // throw new RuntimeException("Could not parse response: " + (BuildConfig.DEBUG ? e.getMessage(): "Unknown error")); @@ -139,26 +128,66 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO return future; } - public static boolean isJwtValid() { - // Claims claims = Jwts.claims().build(); - JwtParser parser = Jwts.parser().build(); - // Jwt jwt = parser.parse(API.getInstance().jwt); + public static CompletableFuture> getBasicData(){ + Request request = new Request.Builder() + .url(BASE_URL + "login") + .addHeader("Authorization", "Bearer " + API.getInstance().jws.toString()) + .build(); + + final CompletableFuture future = new CompletableFuture<>(); + API.getInstance().client.newCall(request).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + if (response.code() == 200) { + try { + //log data to console + Log.i("API_TAG", response.toString()); + JSONObject json = new JSONObject(response.body().string()); + + //here I take the data I need, so: + // FirstName = { user{ firstName } } + // LastName = { user{ lastName } } + // Type of degree + // Student's type + // Year of study = {user { } } AnnoCorso + // Enrolment date + // Degree's Programme + // Study System + // Part-time + + } catch (JSONException e) { + API.getInstance().isLogged = false; + Log.w("TAG_API", (BuildConfig.DEBUG ? "Could not parse response: " + e.getMessage() : "Unknown error")); + throw new RuntimeException("Could not parse response: " + (BuildConfig.DEBUG ? e.getMessage(): "Unknown error")); + } + } + } + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + throw new RuntimeException("Could not fetch data: " + (BuildConfig.DEBUG ? e.getMessage() : "Server error")); + } + }); + return null; + } + public static CompletableFuture getPhoto(){ + + } - return false; - // return claims.getExpiration().after(Date.from(Instant.now())); + public static boolean isValidJws() { + // Controllo se la scadenza è dopo del momento attuale + return API.getInstance() + .jws.getPayload() + .getExpiration() + .after(Date.from(Instant.now())); } - public static boolean refreshJwt() { + public static boolean refreshJws() { OkHttpClient client = getInstance().client; Request request = new Request.Builder() - .build(); throw new UnsupportedOperationException("TODO"); } - public static String getBasicData(){ - return API.getInstance().jwt.toString(); - } } From dd2acd17705420837e02bf86f736928b3daaa234 Mon Sep 17 00:00:00 2001 From: wapeety Date: Mon, 13 Nov 2023 10:54:13 +0100 Subject: [PATCH 02/12] added a comment: see API.java line 148 --- .idea/.name | 1 + app/src/main/java/models/API/API.java | 7 ++++--- app/src/main/java/models/API/Persona.java | 7 +++++++ 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 .idea/.name create mode 100644 app/src/main/java/models/API/Persona.java diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..cd68115 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Esse4 \ No newline at end of file diff --git a/app/src/main/java/models/API/API.java b/app/src/main/java/models/API/API.java index 6a9e433..5b576fb 100644 --- a/app/src/main/java/models/API/API.java +++ b/app/src/main/java/models/API/API.java @@ -145,15 +145,16 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO JSONObject json = new JSONObject(response.body().string()); //here I take the data I need, so: + // Matricola = { user{ userId } } // FirstName = { user{ firstName } } // LastName = { user{ lastName } } // Type of degree // Student's type - // Year of study = {user { } } AnnoCorso + // Year of study = {user { trattiCarriera[ 0{ dettaglioTratto{ annoCorso } } ] } } // Enrolment date - // Degree's Programme + // Degree's Programme = "[" + {user { trattiCarriera[ 0{ dettaglioTratto{ cdsCod } } ] } } + "] - " + {user { trattiCarriera[ 0{ cdsDes } ] } } // Study System - // Part-time + // Part-time = {user { trattiCarriera[ 0{ dettaglioTratto{ ptFlag } } ] } } } catch (JSONException e) { API.getInstance().isLogged = false; diff --git a/app/src/main/java/models/API/Persona.java b/app/src/main/java/models/API/Persona.java new file mode 100644 index 0000000..e5893f4 --- /dev/null +++ b/app/src/main/java/models/API/Persona.java @@ -0,0 +1,7 @@ +package models.API; + +import retrofit2.http.GET; + +public class Persona { + @GET +} From e5eaad8f07971ff8d8a568bef0cbb0eec182746b Mon Sep 17 00:00:00 2001 From: Alvise Favero Date: Mon, 13 Nov 2023 11:50:38 +0100 Subject: [PATCH 03/12] Test non funzionante di Retrofit --- app/build.gradle | 4 + app/src/main/java/models/API/API.java | 4 +- app/src/main/java/models/API/Esse3Api.java | 14 ++ app/src/main/java/models/Persona.java | 220 ++++++++++++++++++ .../esse4/activities/LoginActivity.java | 2 +- .../org/epic_guys/esse4/RetrofitTest.java | 45 ++++ 6 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/models/API/Esse3Api.java create mode 100644 app/src/main/java/models/Persona.java create mode 100644 app/src/test/java/org/epic_guys/esse4/RetrofitTest.java diff --git a/app/build.gradle b/app/build.gradle index 12dcd0e..a59b04e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,10 +42,14 @@ dependencies { implementation 'org.conscrypt:conscrypt-android:2.5.2' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.okhttp3:okhttp:4.11.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // JWT implementation 'io.jsonwebtoken:jjwt-api:0.12.3' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' + + implementation 'com.google.code.gson:gson:2.10.1' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/app/src/main/java/models/API/API.java b/app/src/main/java/models/API/API.java index 5b576fb..210f979 100644 --- a/app/src/main/java/models/API/API.java +++ b/app/src/main/java/models/API/API.java @@ -172,10 +172,10 @@ public void onFailure(@NotNull Call call, @NotNull IOException e) { } public static CompletableFuture getPhoto(){ - + throw new UnsupportedOperationException(); } - public static boolean isValidJws() { + public static boolean isValidJws() { // Controllo se la scadenza è dopo del momento attuale return API.getInstance() .jws.getPayload() diff --git a/app/src/main/java/models/API/Esse3Api.java b/app/src/main/java/models/API/Esse3Api.java new file mode 100644 index 0000000..0f211a9 --- /dev/null +++ b/app/src/main/java/models/API/Esse3Api.java @@ -0,0 +1,14 @@ +package models.API; + +import models.Persona; +import retrofit2.Call; +import retrofit2.http.GET; + +import java.util.List; + +public interface Esse3Api { + + + @GET("anagrafica-service-v2/persone") + Call> getPersone(); +} diff --git a/app/src/main/java/models/Persona.java b/app/src/main/java/models/Persona.java new file mode 100644 index 0000000..f9d06bd --- /dev/null +++ b/app/src/main/java/models/Persona.java @@ -0,0 +1,220 @@ +package models; + +public class Persona { + private String persId; + private String cognome; + private String nome; + private String dataNascita; + private String comuNascCodCatastale; + private String comuNascDes; + private String citstraNasc; + private String comuNascSigla; + private String provNascDes; + private String naziNascNazioneCod; + private String naziNascDes; + private String codFis; + private String sesso; + private String userId; + private String fotoId; + private String naziResCod; + private String naziResDes; + private String comuResCod; + private String comuResDes; + private String comuResSigla; + private String viaRes; + private String numCivRes; + private String capRes; + private String telRes; + private String naziDomCod; + private String naziDomDes; + private String comuDomCod; + private String comuDomDes; + private String comuDomSigla; + private String viaDom; + private String numCivDom; + private String capDom; + private String telDom; + private String email; + private String emailAte; + private String dataIns; + private String dataMod; + private String codCittadinanza; + private String desCittadinanza; + private String cellulare; + private String permsogScadutoFlg; + private String presenzaPermSogFlg; + private String permsogDataScad; + + + public String getCognome() { + return cognome; + } + + public String getNome() { + return nome; + } + + public String getDataNascita() { + return dataNascita; + } + + public String getComuNascCodCatastale() { + return comuNascCodCatastale; + } + + public String getComuNascDes() { + return comuNascDes; + } + + public String getCitstraNasc() { + return citstraNasc; + } + + public String getComuNascSigla() { + return comuNascSigla; + } + + public String getProvNascDes() { + return provNascDes; + } + + public String getNaziNascNazioneCod() { + return naziNascNazioneCod; + } + + public String getNaziNascDes() { + return naziNascDes; + } + + public String getCodFis() { + return codFis; + } + + public String getSesso() { + return sesso; + } + + public String getUserId() { + return userId; + } + + public String getFotoId() { + return fotoId; + } + + public String getNaziResCod() { + return naziResCod; + } + + public String getNaziResDes() { + return naziResDes; + } + + public String getComuResCod() { + return comuResCod; + } + + public String getComuResDes() { + return comuResDes; + } + + public String getComuResSigla() { + return comuResSigla; + } + + public String getViaRes() { + return viaRes; + } + + public String getNumCivRes() { + return numCivRes; + } + + public String getCapRes() { + return capRes; + } + + public String getTelRes() { + return telRes; + } + + public String getNaziDomCod() { + return naziDomCod; + } + + public String getNaziDomDes() { + return naziDomDes; + } + + public String getComuDomCod() { + return comuDomCod; + } + + public String getComuDomDes() { + return comuDomDes; + } + + public String getComuDomSigla() { + return comuDomSigla; + } + + public String getViaDom() { + return viaDom; + } + + public String getNumCivDom() { + return numCivDom; + } + + public String getCapDom() { + return capDom; + } + + public String getTelDom() { + return telDom; + } + + public String getEmail() { + return email; + } + + public String getEmailAte() { + return emailAte; + } + + public String getDataIns() { + return dataIns; + } + + public String getDataMod() { + return dataMod; + } + + public String getCodCittadinanza() { + return codCittadinanza; + } + + public String getDesCittadinanza() { + return desCittadinanza; + } + + public String getCellulare() { + return cellulare; + } + + public String getPermsogScadutoFlg() { + return permsogScadutoFlg; + } + + public String getPresenzaPermSogFlg() { + return presenzaPermSogFlg; + } + + public String getPermsogDataScad() { + return permsogDataScad; + } + + public String getPersId() { + return persId; + } +} diff --git a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java index 02ce544..8d04a85 100644 --- a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java +++ b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java @@ -31,7 +31,7 @@ protected void onCreate(Bundle savedInstanceState) { API.login(matricola, password).thenAccept(isLogged -> { if(isLogged) { TextView fullname_view = findViewById(R.id.text_fullname); - fullname_view.setText(API.getBasicData()); + fullname_view.setText(API.getBasicData().join().get("nome")); // FIXME Questo toast rompe tutto, non so perché runOnUiThread(() -> Toast.makeText(getApplicationContext(), "Login effettuato", Toast.LENGTH_SHORT).show() diff --git a/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java b/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java new file mode 100644 index 0000000..d4822fc --- /dev/null +++ b/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java @@ -0,0 +1,45 @@ +package org.epic_guys.esse4; + +import models.API.Esse3Api; +import models.Persona; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.Test; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +import java.io.IOException; +import java.util.List; + +public class RetrofitTest { + @Test + public void testPersona() throws IOException { + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(chain -> { + String auth = Credentials.basic("REDACTED", "REDACTED"); + Request original = chain.request(); + Request authenticated = original.newBuilder() + .addHeader("Authorization", auth) + .build(); + return chain.proceed(authenticated); + }) + .build(); + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://esse3.unive.it/e3rest/api/") + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build(); + + Esse3Api esse3Api = retrofit.create(Esse3Api.class); + retrofit2.Call> persone = esse3Api.getPersone(); + System.out.println(persone.request().url()); + retrofit2.Response> response = persone.execute(); + System.out.println(response.raw()); + for (Persona p : response.body()) { + System.out.println(p.getNome() + " " + p.getCognome()); + } + } +} From 1fe7302154163eefeb425684f09dcc0d43248555 Mon Sep 17 00:00:00 2001 From: Alvise Favero Date: Mon, 13 Nov 2023 21:46:30 +0100 Subject: [PATCH 04/12] =?UTF-8?q?Inizio=20Retrofit,=20JWK=20un=20po'=20pi?= =?UTF-8?q?=C3=B9=20funzionanti?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 +- app/src/main/java/models/API/API.java | 194 --------------- app/src/main/java/models/API/Esse3Api.java | 14 -- app/src/main/java/models/API/Persona.java | 7 - .../java/org/epic_guys/esse4/API/API.java | 220 ++++++++++++++++++ .../API/services/AnagraficheService.java | 14 ++ .../esse4/API/services/ApiService.java | 7 + .../esse4/activities/LoginActivity.java | 6 +- .../epic_guys/esse4}/models/Persona.java | 2 +- .../org/epic_guys/esse4/RetrofitTest.java | 26 ++- 10 files changed, 259 insertions(+), 233 deletions(-) delete mode 100644 app/src/main/java/models/API/API.java delete mode 100644 app/src/main/java/models/API/Esse3Api.java delete mode 100644 app/src/main/java/models/API/Persona.java create mode 100644 app/src/main/java/org/epic_guys/esse4/API/API.java create mode 100644 app/src/main/java/org/epic_guys/esse4/API/services/AnagraficheService.java create mode 100644 app/src/main/java/org/epic_guys/esse4/API/services/ApiService.java rename app/src/main/java/{ => org/epic_guys/esse4}/models/Persona.java (99%) diff --git a/app/build.gradle b/app/build.gradle index a59b04e..d00a62a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,7 +46,7 @@ dependencies { // JWT implementation 'io.jsonwebtoken:jjwt-api:0.12.3' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-gson:0.12.3' implementation 'com.google.code.gson:gson:2.10.1' diff --git a/app/src/main/java/models/API/API.java b/app/src/main/java/models/API/API.java deleted file mode 100644 index 210f979..0000000 --- a/app/src/main/java/models/API/API.java +++ /dev/null @@ -1,194 +0,0 @@ -package models.API; - -import android.graphics.Picture; -import android.util.Log; - -import org.conscrypt.BuildConfig; -import org.jetbrains.annotations.NotNull; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.security.PublicKey; -import java.time.Instant; -import java.util.Date; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import io.jsonwebtoken.JwtParser; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.io.Parser; -import io.jsonwebtoken.security.Jwk; -import io.jsonwebtoken.security.Jwks; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Credentials; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -public class API { - private static API instance; - private OkHttpClient client; - private JwtParser jwtParser; - private Jws jws; - private Jwk jwk; - private boolean isLogged = false; - public static final String BASE_URL = "https://esse3.unive.it/e3rest/api/"; - - - private API() { - this.client = new OkHttpClient.Builder().build(); - } - - public static API getInstance(){ - return instance == null ? instance = new API() : instance; - } - - @NotNull - public static CompletableFuture saveJwk() { - Request request = new Request.Builder() - .url(BASE_URL + "jwt/jwk") - .build(); - - final CompletableFuture future = new CompletableFuture<>(); - API.getInstance().client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - future.complete(false); - } - - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - Parser> parser = Jwks.parser().build(); - API.getInstance().jwk = (Jwk) parser.parse(response.body().string()); - API.getInstance().jwtParser = Jwts.parser() - .verifyWith(API.getInstance().jwk.toKey()) - .build(); - future.complete(true); - } - }); - - return future; - } - - @NotNull - public static CompletableFuture login (String username, String password) { - String auth = Credentials.basic( - username, - password - ); - - Request request = new Request.Builder() - .url(BASE_URL + "login/jwt/new/") - .addHeader("Authorization", auth) - .build(); - - - final CompletableFuture future = new CompletableFuture<>(); - API.getInstance().client.newCall(request).enqueue(new Callback() { - - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - throw new RuntimeException("Could not perform login: " + (BuildConfig.DEBUG ? e.getMessage() : "Unknown error")); - } - - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - if (response.code() == 200) { - try { - if (!saveJwk().join()) - throw new RuntimeException("Failed to save JWK"); - - - //log data to console - Log.i("API_TAG", response.toString()); - - JSONObject json = new JSONObject(response.body().string()); - - API.getInstance().jws = API.getInstance().jwtParser.parseSignedClaims(json.getString("jwt")); - - - // Meglio mettere qui isLogged in modo che se si lancia un'eccezione resta false - API.getInstance().isLogged = true; - } catch (JSONException e) { - API.getInstance().isLogged = false; - Log.w("TAG_API", (BuildConfig.DEBUG ? "Could not parse response: " + e.getMessage() : "Unknown error")); - // throw new RuntimeException("Could not parse response: " + (BuildConfig.DEBUG ? e.getMessage(): "Unknown error")); - } - } else { - API.getInstance().isLogged = false; - } - future.complete(API.getInstance().isLogged); - } - }); - - return future; - } - - public static CompletableFuture> getBasicData(){ - Request request = new Request.Builder() - .url(BASE_URL + "login") - .addHeader("Authorization", "Bearer " + API.getInstance().jws.toString()) - .build(); - - final CompletableFuture future = new CompletableFuture<>(); - API.getInstance().client.newCall(request).enqueue(new Callback() { - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - if (response.code() == 200) { - try { - //log data to console - Log.i("API_TAG", response.toString()); - JSONObject json = new JSONObject(response.body().string()); - - //here I take the data I need, so: - // Matricola = { user{ userId } } - // FirstName = { user{ firstName } } - // LastName = { user{ lastName } } - // Type of degree - // Student's type - // Year of study = {user { trattiCarriera[ 0{ dettaglioTratto{ annoCorso } } ] } } - // Enrolment date - // Degree's Programme = "[" + {user { trattiCarriera[ 0{ dettaglioTratto{ cdsCod } } ] } } + "] - " + {user { trattiCarriera[ 0{ cdsDes } ] } } - // Study System - // Part-time = {user { trattiCarriera[ 0{ dettaglioTratto{ ptFlag } } ] } } - - } catch (JSONException e) { - API.getInstance().isLogged = false; - Log.w("TAG_API", (BuildConfig.DEBUG ? "Could not parse response: " + e.getMessage() : "Unknown error")); - throw new RuntimeException("Could not parse response: " + (BuildConfig.DEBUG ? e.getMessage(): "Unknown error")); - } - } - } - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - throw new RuntimeException("Could not fetch data: " + (BuildConfig.DEBUG ? e.getMessage() : "Server error")); - } - }); - return null; - } - - public static CompletableFuture getPhoto(){ - throw new UnsupportedOperationException(); - } - - public static boolean isValidJws() { - // Controllo se la scadenza è dopo del momento attuale - return API.getInstance() - .jws.getPayload() - .getExpiration() - .after(Date.from(Instant.now())); - } - - public static boolean refreshJws() { - OkHttpClient client = getInstance().client; - Request request = new Request.Builder() - .build(); - - throw new UnsupportedOperationException("TODO"); - } - -} diff --git a/app/src/main/java/models/API/Esse3Api.java b/app/src/main/java/models/API/Esse3Api.java deleted file mode 100644 index 0f211a9..0000000 --- a/app/src/main/java/models/API/Esse3Api.java +++ /dev/null @@ -1,14 +0,0 @@ -package models.API; - -import models.Persona; -import retrofit2.Call; -import retrofit2.http.GET; - -import java.util.List; - -public interface Esse3Api { - - - @GET("anagrafica-service-v2/persone") - Call> getPersone(); -} diff --git a/app/src/main/java/models/API/Persona.java b/app/src/main/java/models/API/Persona.java deleted file mode 100644 index e5893f4..0000000 --- a/app/src/main/java/models/API/Persona.java +++ /dev/null @@ -1,7 +0,0 @@ -package models.API; - -import retrofit2.http.GET; - -public class Persona { - @GET -} diff --git a/app/src/main/java/org/epic_guys/esse4/API/API.java b/app/src/main/java/org/epic_guys/esse4/API/API.java new file mode 100644 index 0000000..2d55865 --- /dev/null +++ b/app/src/main/java/org/epic_guys/esse4/API/API.java @@ -0,0 +1,220 @@ +package org.epic_guys.esse4.API; + +import android.graphics.Picture; +import android.util.Log; + +import io.jsonwebtoken.*; +import org.epic_guys.esse4.API.services.AnagraficheService; +import org.epic_guys.esse4.API.services.ApiService; +import org.epic_guys.esse4.models.Persona; +import okhttp3.*; +import org.conscrypt.BuildConfig; +import org.jetbrains.annotations.NotNull; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.security.PublicKey; +import java.time.Instant; +import java.util.Date; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.Jwks; +import retrofit2.Retrofit; +import retrofit2.Call; +import retrofit2.Response; +import retrofit2.converter.gson.GsonConverterFactory; + +public class API { + private static API instance; + private OkHttpClient client; + private Retrofit retrofit; + private JwtParser jwtParser; + private Jws jws; + private Jwk jwk; + private boolean isLogged = false; + public static final String BASE_URL = "https://esse3.unive.it/e3rest/api/"; + + + private API() { + this.client = new OkHttpClient.Builder().build(); + + this.retrofit = new Retrofit.Builder() + .client(this.client) + .addConverterFactory(GsonConverterFactory.create()) + .baseUrl(API.BASE_URL) + .build(); + } + + public static API getInstance(){ + return instance == null ? instance = new API() : instance; + } + + + /** + * ROTTA NON USARE. + */ + @NotNull + public static CompletableFuture fetchJwk() { + Request request = new Request.Builder() + .url(BASE_URL + "jwt/jwk") + .build(); + + final CompletableFuture future = new CompletableFuture<>(); + API.getInstance().client.newCall(request).enqueue(new okhttp3.Callback() { + @Override + public void onFailure(@NotNull okhttp3.Call call, @NotNull IOException e) { + future.completeExceptionally(e); + } + + @Override + public void onResponse(@NotNull okhttp3.Call call, @NotNull okhttp3.Response response) throws IOException { + Parser> parser = Jwks.parser().build(); + try { + JSONObject json = new JSONObject(response.body().string()); + String jwk = json.getJSONArray("keys").getJSONObject(0).toString(); + API.getInstance().jwk = (Jwk) parser.parse(jwk); + API.getInstance().jwtParser = Jwts.parser() + .verifyWith(API.getInstance().jwk.toKey()) + .build(); + future.complete(null); + } catch (JSONException e) { + future.completeExceptionally(e); + } + } + }); + + return future; + } + + @NotNull + public static CompletableFuture login (String username, String password) { + String auth = Credentials.basic( + username, + password + ); + + Request request = new Request.Builder() + .url(BASE_URL + "login/jwt/new/") + .addHeader("Authorization", auth) + .build(); + + + final CompletableFuture future = new CompletableFuture<>(); + API.getInstance().client.newCall(request).enqueue(new Callback() { + + @Override + public void onFailure(@NotNull okhttp3.Call call, @NotNull IOException e) { + throw new RuntimeException("Could not perform login: " + (BuildConfig.DEBUG ? e.getMessage() : "Unknown error")); + } + + @Override + public void onResponse(@NotNull okhttp3.Call call, @NotNull okhttp3.Response response) throws IOException { + if (response.code() == 200) { + try { + API.fetchJwk().join(); + + //log data to console + Log.i("API_TAG", response.toString()); + + JSONObject json = new JSONObject(response.body().string()); + String jwt = json.getString("jwt"); + + API.getInstance().jws = API.getInstance().jwtParser.parseSignedClaims(jwt); + + API.getInstance().isLogged = true; + } catch (JSONException e) { + API.getInstance().isLogged = false; + Log.w("TAG_API", (BuildConfig.DEBUG ? "Could not parse response: " + e.getMessage() : "Unknown error")); + future.completeExceptionally(e); + // throw new RuntimeException("Could not parse response: " + (BuildConfig.DEBUG ? e.getMessage(): "Unknown error")); + } + } else { + API.getInstance().isLogged = false; + } + future.complete(API.getInstance().isLogged); + } + }); + + return future; + } + + public static CompletableFuture getBasicData(){ + + final CompletableFuture future = new CompletableFuture<>(); + + API.getService(AnagraficheService.class) + .getPersone() + .enqueue( new retrofit2.Callback>() { + @Override + public void onResponse(@NotNull Call> call, @NotNull Response> response) { + if (response.code() == 200) { + //log data to console + Log.i("API_TAG", response.toString()); + + //here I take the data I need, so: + // Matricola = { user{ userId } } + // FirstName = { user{ firstName } } + // LastName = { user{ lastName } } + // Type of degree + // Student's type + // Year of study = {user { trattiCarriera[ 0{ dettaglioTratto{ annoCorso } } ] } } + // Enrolment date + // Degree's Programme = "[" + {user { trattiCarriera[ 0{ dettaglioTratto{ cdsCod } } ] } } + "] - " + {user { trattiCarriera[ 0{ cdsDes } ] } } + // Study System + // Part-time = {user { trattiCarriera[ 0{ dettaglioTratto{ ptFlag } } ] } } + try { + Persona p = response.body().get(0); + future.complete(p); + } catch (NullPointerException e) { + future.completeExceptionally(e); + } + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + throw new RuntimeException("Could not fetch data: " + (BuildConfig.DEBUG ? t.getMessage() : "Server error")); + } + }); + + return future; + } + + public static CompletableFuture getPhoto(){ + throw new UnsupportedOperationException(); + } + + /** + * @return Whether the expiration date is before the current time. + */ + public static boolean isValidJws() { + return API.getInstance() + .jws.getPayload() + .getExpiration() + .after(Date.from(Instant.now())); + } + + public static boolean refreshJws() { + OkHttpClient client = getInstance().client; + Request request = new Request.Builder() + .build(); + + throw new UnsupportedOperationException("TODO"); + } + + /** + * Provides an instance of the specified service. + * @param serviceClass Class of the service requested. + * @return An instance of the service. + * @param Type of service requested. It must be a subclass of ApiService + * @see ApiService + */ + public static T getService(Class serviceClass) { + return API.getInstance().retrofit.create(serviceClass); + + } +} diff --git a/app/src/main/java/org/epic_guys/esse4/API/services/AnagraficheService.java b/app/src/main/java/org/epic_guys/esse4/API/services/AnagraficheService.java new file mode 100644 index 0000000..349d42b --- /dev/null +++ b/app/src/main/java/org/epic_guys/esse4/API/services/AnagraficheService.java @@ -0,0 +1,14 @@ +package org.epic_guys.esse4.API.services; + +import org.epic_guys.esse4.models.Persona; +import retrofit2.Call; +import retrofit2.http.GET; + +import java.util.List; + +public interface AnagraficheService extends ApiService { + String BASE_URL = "anagrafica-service-v2"; + + @GET(BASE_URL + "/persone") + Call> getPersone(); +} diff --git a/app/src/main/java/org/epic_guys/esse4/API/services/ApiService.java b/app/src/main/java/org/epic_guys/esse4/API/services/ApiService.java new file mode 100644 index 0000000..47a4673 --- /dev/null +++ b/app/src/main/java/org/epic_guys/esse4/API/services/ApiService.java @@ -0,0 +1,7 @@ +package org.epic_guys.esse4.API.services; + +/** + * Base interface for all API services. + */ +public interface ApiService { +} diff --git a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java index 8d04a85..63ffd20 100644 --- a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java +++ b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java @@ -1,6 +1,5 @@ package org.epic_guys.esse4.activities; -import android.content.Context; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.Button; @@ -10,7 +9,7 @@ import org.epic_guys.esse4.R; -import models.API.API; +import org.epic_guys.esse4.API.API; public class LoginActivity extends AppCompatActivity { @@ -31,8 +30,7 @@ protected void onCreate(Bundle savedInstanceState) { API.login(matricola, password).thenAccept(isLogged -> { if(isLogged) { TextView fullname_view = findViewById(R.id.text_fullname); - fullname_view.setText(API.getBasicData().join().get("nome")); - // FIXME Questo toast rompe tutto, non so perché + fullname_view.setText(API.getBasicData().join().getNome()); runOnUiThread(() -> Toast.makeText(getApplicationContext(), "Login effettuato", Toast.LENGTH_SHORT).show() ); diff --git a/app/src/main/java/models/Persona.java b/app/src/main/java/org/epic_guys/esse4/models/Persona.java similarity index 99% rename from app/src/main/java/models/Persona.java rename to app/src/main/java/org/epic_guys/esse4/models/Persona.java index f9d06bd..f630404 100644 --- a/app/src/main/java/models/Persona.java +++ b/app/src/main/java/org/epic_guys/esse4/models/Persona.java @@ -1,4 +1,4 @@ -package models; +package org.epic_guys.esse4.models; public class Persona { private String persId; diff --git a/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java b/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java index d4822fc..ab840c5 100644 --- a/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java +++ b/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java @@ -1,10 +1,8 @@ package org.epic_guys.esse4; -import models.API.Esse3Api; -import models.Persona; +import org.epic_guys.esse4.API.services.AnagraficheService; +import org.epic_guys.esse4.models.Persona; import okhttp3.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.junit.Test; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; @@ -13,10 +11,11 @@ import java.util.List; public class RetrofitTest { - @Test - public void testPersona() throws IOException { + private OkHttpClient client; + private Retrofit retrofit; - OkHttpClient client = new OkHttpClient.Builder() + public RetrofitTest() { + client = new OkHttpClient.Builder() .addInterceptor(chain -> { String auth = Credentials.basic("REDACTED", "REDACTED"); Request original = chain.request(); @@ -27,19 +26,22 @@ public void testPersona() throws IOException { }) .build(); - Retrofit retrofit = new Retrofit.Builder() + retrofit = new Retrofit.Builder() .baseUrl("https://esse3.unive.it/e3rest/api/") .addConverterFactory(GsonConverterFactory.create()) .client(client) .build(); + } - Esse3Api esse3Api = retrofit.create(Esse3Api.class); + @Test + public void testPersona() throws IOException { + AnagraficheService esse3Api = retrofit.create(AnagraficheService.class); retrofit2.Call> persone = esse3Api.getPersone(); System.out.println(persone.request().url()); retrofit2.Response> response = persone.execute(); System.out.println(response.raw()); - for (Persona p : response.body()) { - System.out.println(p.getNome() + " " + p.getCognome()); - } + // for (Persona p : response.body()) { + // System.out.println(p.getNome() + " " + p.getCognome()); + // } } } From 4e4b97ee9d67913a2e453adb6c7e8f267150e396 Mon Sep 17 00:00:00 2001 From: Alvise Favero Date: Tue, 14 Nov 2023 00:16:13 +0100 Subject: [PATCH 05/12] Autenticazione tramite JWT funzionante, droppata JJWT --- .../java/org/epic_guys/esse4/API/API.java | 86 +++++++++++-------- .../esse4/API/services/JwtService.java | 10 +++ .../esse4/activities/LoginActivity.java | 20 +++-- .../java/org/epic_guys/esse4/models/Jwt.java | 38 ++++++++ 4 files changed, 108 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/org/epic_guys/esse4/API/services/JwtService.java create mode 100644 app/src/main/java/org/epic_guys/esse4/models/Jwt.java diff --git a/app/src/main/java/org/epic_guys/esse4/API/API.java b/app/src/main/java/org/epic_guys/esse4/API/API.java index 2d55865..116ffc1 100644 --- a/app/src/main/java/org/epic_guys/esse4/API/API.java +++ b/app/src/main/java/org/epic_guys/esse4/API/API.java @@ -6,6 +6,8 @@ import io.jsonwebtoken.*; import org.epic_guys.esse4.API.services.AnagraficheService; import org.epic_guys.esse4.API.services.ApiService; +import org.epic_guys.esse4.API.services.JwtService; +import org.epic_guys.esse4.models.Jwt; import org.epic_guys.esse4.models.Persona; import okhttp3.*; import org.conscrypt.BuildConfig; @@ -32,15 +34,23 @@ public class API { private static API instance; private OkHttpClient client; private Retrofit retrofit; - private JwtParser jwtParser; - private Jws jws; - private Jwk jwk; + private Jwt jwt; private boolean isLogged = false; public static final String BASE_URL = "https://esse3.unive.it/e3rest/api/"; private API() { - this.client = new OkHttpClient.Builder().build(); + this.client = new OkHttpClient.Builder() + .addInterceptor(chain -> { + Request request = chain.request(); + if (jwt != null) { + request = chain.request().newBuilder() + .addHeader("Authorization", "Bearer " + jwt.getJwt()) + .build(); + } + return chain.proceed(request); + }) + .build(); this.retrofit = new Retrofit.Builder() .client(this.client) @@ -54,9 +64,7 @@ public static API getInstance(){ } - /** - * ROTTA NON USARE. - */ + /* @NotNull public static CompletableFuture fetchJwk() { Request request = new Request.Builder() @@ -89,49 +97,51 @@ public void onResponse(@NotNull okhttp3.Call call, @NotNull okhttp3.Response res return future; } + */ @NotNull public static CompletableFuture login (String username, String password) { + final CompletableFuture future = new CompletableFuture<>(); + String auth = Credentials.basic( username, password ); - Request request = new Request.Builder() - .url(BASE_URL + "login/jwt/new/") - .addHeader("Authorization", auth) + // We use a new one only this time, to use basic authentication + OkHttpClient basicClient = new OkHttpClient.Builder() + .addInterceptor(chain -> { + Request authenticated = chain.request().newBuilder() + .addHeader("Authorization", auth) + .build(); + return chain.proceed(authenticated); + }) + .build(); + Retrofit basicRetrofit = new Retrofit.Builder() + .client(basicClient) + .baseUrl(API.BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) .build(); - final CompletableFuture future = new CompletableFuture<>(); - API.getInstance().client.newCall(request).enqueue(new Callback() { - + basicRetrofit.create(JwtService.class) + .newJwt() + .enqueue(new retrofit2.Callback() { @Override - public void onFailure(@NotNull okhttp3.Call call, @NotNull IOException e) { - throw new RuntimeException("Could not perform login: " + (BuildConfig.DEBUG ? e.getMessage() : "Unknown error")); + public void onFailure(@NotNull Call call, @NotNull Throwable t) { + // throw new RuntimeException("Could not perform login: " + (BuildConfig.DEBUG ? t.getMessage() : "Unknown error")); + future.completeExceptionally(t); } @Override - public void onResponse(@NotNull okhttp3.Call call, @NotNull okhttp3.Response response) throws IOException { + public void onResponse(@NotNull Call call, @NotNull Response response) { if (response.code() == 200) { - try { - API.fetchJwk().join(); - - //log data to console - Log.i("API_TAG", response.toString()); - - JSONObject json = new JSONObject(response.body().string()); - String jwt = json.getString("jwt"); - - API.getInstance().jws = API.getInstance().jwtParser.parseSignedClaims(jwt); - - API.getInstance().isLogged = true; - } catch (JSONException e) { - API.getInstance().isLogged = false; - Log.w("TAG_API", (BuildConfig.DEBUG ? "Could not parse response: " + e.getMessage() : "Unknown error")); - future.completeExceptionally(e); - // throw new RuntimeException("Could not parse response: " + (BuildConfig.DEBUG ? e.getMessage(): "Unknown error")); - } + // API.fetchJwk().join(); + + //log data to console + Log.i("API_TAG", response.toString()); + API.getInstance().jwt = response.body(); + API.getInstance().isLogged = true; } else { API.getInstance().isLogged = false; } @@ -177,7 +187,8 @@ public void onResponse(@NotNull Call> call, @NotNull Response> call, Throwable t) { - throw new RuntimeException("Could not fetch data: " + (BuildConfig.DEBUG ? t.getMessage() : "Server error")); + // throw new RuntimeException("Could not fetch data: " + (BuildConfig.DEBUG ? t.getMessage() : "Server error")); + future.completeExceptionally(t); } }); @@ -191,10 +202,9 @@ public static CompletableFuture getPhoto(){ /** * @return Whether the expiration date is before the current time. */ - public static boolean isValidJws() { + public static boolean isValidJwt() { return API.getInstance() - .jws.getPayload() - .getExpiration() + .jwt.getExpiration() .after(Date.from(Instant.now())); } diff --git a/app/src/main/java/org/epic_guys/esse4/API/services/JwtService.java b/app/src/main/java/org/epic_guys/esse4/API/services/JwtService.java new file mode 100644 index 0000000..e1d52b6 --- /dev/null +++ b/app/src/main/java/org/epic_guys/esse4/API/services/JwtService.java @@ -0,0 +1,10 @@ +package org.epic_guys.esse4.API.services; + +import org.epic_guys.esse4.models.Jwt; +import retrofit2.Call; +import retrofit2.http.GET; + +public interface JwtService extends ApiService { + @GET("login/jwt/new") + Call newJwt(); +} diff --git a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java index 63ffd20..3349986 100644 --- a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java +++ b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java @@ -27,19 +27,23 @@ protected void onCreate(Bundle savedInstanceState) { String matricola = matricolaInput.getText().toString(); String password = passwordInput.getText().toString(); - API.login(matricola, password).thenAccept(isLogged -> { - if(isLogged) { - TextView fullname_view = findViewById(R.id.text_fullname); - fullname_view.setText(API.getBasicData().join().getNome()); - runOnUiThread(() -> - Toast.makeText(getApplicationContext(), "Login effettuato", Toast.LENGTH_SHORT).show() - ); - } else { + API.login(matricola, password).thenComposeAsync(isLogged -> { + if (isLogged) + return API.getBasicData(); + else { runOnUiThread(() -> Toast.makeText(getApplicationContext(), "Login fallito, proprio come te", Toast.LENGTH_SHORT).show() ); + throw new RuntimeException("Failed to login"); } + }).thenAccept(persona -> { + TextView fullname_view = findViewById(R.id.text_fullname); + fullname_view.setText(persona.getNome()); + runOnUiThread(() -> + Toast.makeText(getApplicationContext(), "Login effettuato", Toast.LENGTH_SHORT).show() + ); }); + }); } } \ No newline at end of file diff --git a/app/src/main/java/org/epic_guys/esse4/models/Jwt.java b/app/src/main/java/org/epic_guys/esse4/models/Jwt.java new file mode 100644 index 0000000..82488ba --- /dev/null +++ b/app/src/main/java/org/epic_guys/esse4/models/Jwt.java @@ -0,0 +1,38 @@ +package org.epic_guys.esse4.models; + +import com.google.gson.Gson; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +public class Jwt { + private final String jwt; + private transient Map payload; + + public Jwt(String jwt) { + this.jwt = jwt; + getPayload(); + } + + @NotNull + private Map getPayload() { + if (payload == null) { + Gson gson = new Gson(); + String payload = jwt.split("\\.")[2]; + this.payload = gson.fromJson(payload, Map.class); + } + return payload; + } + + public String get(String key) { + return getPayload().get(key); + } + + public String getJwt() { + return jwt; + } + + public Date getExpiration() { + return new Date(Long.decode(getPayload().get("exp"))); + } +} From bf2ff07278561632dc7141e478284c9d3758cdab Mon Sep 17 00:00:00 2001 From: Alvise Favero Date: Tue, 14 Nov 2023 09:29:36 +0100 Subject: [PATCH 06/12] JWT refresh, fix JWT decoding --- .../java/org/epic_guys/esse4/API/API.java | 54 +++++++++++++------ .../esse4/API/services/JwtService.java | 4 ++ .../esse4/exceptions/ApiException.java | 22 ++++++++ .../epic_guys/esse4/models/ApiResource.java | 4 ++ .../java/org/epic_guys/esse4/models/Jwt.java | 12 ++++- .../org/epic_guys/esse4/models/Persona.java | 2 +- 6 files changed, 79 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/org/epic_guys/esse4/exceptions/ApiException.java create mode 100644 app/src/main/java/org/epic_guys/esse4/models/ApiResource.java diff --git a/app/src/main/java/org/epic_guys/esse4/API/API.java b/app/src/main/java/org/epic_guys/esse4/API/API.java index 116ffc1..db8730a 100644 --- a/app/src/main/java/org/epic_guys/esse4/API/API.java +++ b/app/src/main/java/org/epic_guys/esse4/API/API.java @@ -3,28 +3,23 @@ import android.graphics.Picture; import android.util.Log; -import io.jsonwebtoken.*; +import okhttp3.OkHttpClient; +import okhttp3.Credentials; +import okhttp3.Request; import org.epic_guys.esse4.API.services.AnagraficheService; import org.epic_guys.esse4.API.services.ApiService; import org.epic_guys.esse4.API.services.JwtService; +import org.epic_guys.esse4.exceptions.ApiException; import org.epic_guys.esse4.models.Jwt; import org.epic_guys.esse4.models.Persona; -import okhttp3.*; -import org.conscrypt.BuildConfig; import org.jetbrains.annotations.NotNull; -import org.json.JSONException; -import org.json.JSONObject; -import java.io.IOException; -import java.security.PublicKey; import java.time.Instant; import java.util.Date; import java.util.List; import java.util.concurrent.CompletableFuture; -import io.jsonwebtoken.io.Parser; -import io.jsonwebtoken.security.Jwk; -import io.jsonwebtoken.security.Jwks; +import retrofit2.Callback; import retrofit2.Retrofit; import retrofit2.Call; import retrofit2.Response; @@ -38,14 +33,19 @@ public class API { private boolean isLogged = false; public static final String BASE_URL = "https://esse3.unive.it/e3rest/api/"; + private Jwt getJwt() { + return jwt; + } + private API() { this.client = new OkHttpClient.Builder() .addInterceptor(chain -> { Request request = chain.request(); + Jwt jwt = this.getJwt(); if (jwt != null) { request = chain.request().newBuilder() - .addHeader("Authorization", "Bearer " + jwt.getJwt()) + .addHeader("Authorization", "Bearer " + jwt) .build(); } return chain.proceed(request); @@ -208,12 +208,34 @@ public static boolean isValidJwt() { .after(Date.from(Instant.now())); } - public static boolean refreshJws() { - OkHttpClient client = getInstance().client; - Request request = new Request.Builder() - .build(); + /** + * Refreshes the current JWT and stores it. + */ + public static CompletableFuture refreshJwt() { + final CompletableFuture future = new CompletableFuture<>(); + JwtService jwtService = API.getService(JwtService.class); + Jwt jwt = API.getInstance().jwt; + jwtService.refreshJwt(jwt.toString()) + .enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + if (response.isSuccessful()) { + API.getInstance().jwt = response.body(); + future.complete(null); + } + else { + future.completeExceptionally( + new ApiException("Response " + response.code() + " when refreshing JWT.")); + } + } - throw new UnsupportedOperationException("TODO"); + @Override + public void onFailure(@NotNull Call call, @NotNull Throwable t) { + future.completeExceptionally(t); + } + }); + + return future; } /** diff --git a/app/src/main/java/org/epic_guys/esse4/API/services/JwtService.java b/app/src/main/java/org/epic_guys/esse4/API/services/JwtService.java index e1d52b6..63187c9 100644 --- a/app/src/main/java/org/epic_guys/esse4/API/services/JwtService.java +++ b/app/src/main/java/org/epic_guys/esse4/API/services/JwtService.java @@ -3,8 +3,12 @@ import org.epic_guys.esse4.models.Jwt; import retrofit2.Call; import retrofit2.http.GET; +import retrofit2.http.Query; public interface JwtService extends ApiService { @GET("login/jwt/new") Call newJwt(); + + @GET("jwt/refresh") + Call refreshJwt(@Query("jwt") String jwt); } diff --git a/app/src/main/java/org/epic_guys/esse4/exceptions/ApiException.java b/app/src/main/java/org/epic_guys/esse4/exceptions/ApiException.java new file mode 100644 index 0000000..edf5f2e --- /dev/null +++ b/app/src/main/java/org/epic_guys/esse4/exceptions/ApiException.java @@ -0,0 +1,22 @@ +package org.epic_guys.esse4.exceptions; + +public class ApiException extends RuntimeException { + public ApiException() { + } + + public ApiException(String message) { + super(message); + } + + public ApiException(String message, Throwable cause) { + super(message, cause); + } + + public ApiException(Throwable cause) { + super(cause); + } + + public ApiException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/app/src/main/java/org/epic_guys/esse4/models/ApiResource.java b/app/src/main/java/org/epic_guys/esse4/models/ApiResource.java new file mode 100644 index 0000000..7398871 --- /dev/null +++ b/app/src/main/java/org/epic_guys/esse4/models/ApiResource.java @@ -0,0 +1,4 @@ +package org.epic_guys.esse4.models; + +public interface ApiResource { +} diff --git a/app/src/main/java/org/epic_guys/esse4/models/Jwt.java b/app/src/main/java/org/epic_guys/esse4/models/Jwt.java index 82488ba..0fdf91f 100644 --- a/app/src/main/java/org/epic_guys/esse4/models/Jwt.java +++ b/app/src/main/java/org/epic_guys/esse4/models/Jwt.java @@ -5,7 +5,7 @@ import java.util.*; -public class Jwt { +public class Jwt implements ApiResource { private final String jwt; private transient Map payload; @@ -18,7 +18,9 @@ public Jwt(String jwt) { private Map getPayload() { if (payload == null) { Gson gson = new Gson(); + Base64.Decoder base64 = Base64.getDecoder(); String payload = jwt.split("\\.")[2]; + payload = new String(base64.decode(payload)); this.payload = gson.fromJson(payload, Map.class); } return payload; @@ -28,7 +30,13 @@ public String get(String key) { return getPayload().get(key); } - public String getJwt() { + + /** + * @return The JWT as a String. + */ + @NotNull + @Override + public String toString() { return jwt; } diff --git a/app/src/main/java/org/epic_guys/esse4/models/Persona.java b/app/src/main/java/org/epic_guys/esse4/models/Persona.java index f630404..d7e53d9 100644 --- a/app/src/main/java/org/epic_guys/esse4/models/Persona.java +++ b/app/src/main/java/org/epic_guys/esse4/models/Persona.java @@ -1,6 +1,6 @@ package org.epic_guys.esse4.models; -public class Persona { +public class Persona implements ApiResource { private String persId; private String cognome; private String nome; From 9e391bb6b14bddeb2fce19798186bbb308bcc2ae Mon Sep 17 00:00:00 2001 From: Alvise Favero Date: Tue, 14 Nov 2023 17:53:50 +0100 Subject: [PATCH 07/12] REAL fix JWT (I tested it) --- .../java/org/epic_guys/esse4/API/API.java | 2 +- .../java/org/epic_guys/esse4/models/Jwt.java | 81 +++++++++++++++---- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/epic_guys/esse4/API/API.java b/app/src/main/java/org/epic_guys/esse4/API/API.java index db8730a..5147ff8 100644 --- a/app/src/main/java/org/epic_guys/esse4/API/API.java +++ b/app/src/main/java/org/epic_guys/esse4/API/API.java @@ -204,7 +204,7 @@ public static CompletableFuture getPhoto(){ */ public static boolean isValidJwt() { return API.getInstance() - .jwt.getExpiration() + .jwt.getPayload().getExpirationTime() .after(Date.from(Instant.now())); } diff --git a/app/src/main/java/org/epic_guys/esse4/models/Jwt.java b/app/src/main/java/org/epic_guys/esse4/models/Jwt.java index 0fdf91f..b1bf952 100644 --- a/app/src/main/java/org/epic_guys/esse4/models/Jwt.java +++ b/app/src/main/java/org/epic_guys/esse4/models/Jwt.java @@ -1,13 +1,71 @@ package org.epic_guys.esse4.models; import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; import org.jetbrains.annotations.NotNull; import java.util.*; public class Jwt implements ApiResource { private final String jwt; - private transient Map payload; + + public static class Payload { + @SerializedName("iss") + private String issuer; + + @SerializedName("sub") + private String subject; + + @SerializedName("aud") + private String audience; + + @SerializedName("exp") + private long expirationTime; + + @SerializedName("nbf") + private long notBefore; + + @SerializedName("iat") + private long issuedAt; + + @SerializedName("jti") + private String jwtId; + + public String getIssuer() { + return issuer; + } + + public String getSubject() { + return subject; + } + + public String getAudience() { + return audience; + } + + public Date getExpirationTime() { + /* + * Java essere tipo: molto interessante il tuo epoch in secondi, + * peccato che io lo voglio in millisecondi. + */ + return new Date(expirationTime * 1000); + } + + public long getNotBefore() { + return notBefore; + } + + public long getIssuedAt() { + return issuedAt; + } + + public String getJwtId() { + return jwtId; + } + } + + private transient Payload payload; + public Jwt(String jwt) { this.jwt = jwt; @@ -15,22 +73,17 @@ public Jwt(String jwt) { } @NotNull - private Map getPayload() { - if (payload == null) { + public Payload getPayload() { + if (this.payload == null) { Gson gson = new Gson(); - Base64.Decoder base64 = Base64.getDecoder(); - String payload = jwt.split("\\.")[2]; + Base64.Decoder base64 = Base64.getUrlDecoder(); + String payload = jwt.split("\\.")[1]; payload = new String(base64.decode(payload)); - this.payload = gson.fromJson(payload, Map.class); + this.payload = gson.fromJson(payload, Payload.class); } - return payload; - } - - public String get(String key) { - return getPayload().get(key); + return this.payload; } - /** * @return The JWT as a String. */ @@ -39,8 +92,4 @@ public String get(String key) { public String toString() { return jwt; } - - public Date getExpiration() { - return new Date(Long.decode(getPayload().get("exp"))); - } } From 681f97995cecb0a2ffad871731b1d9de401ed122 Mon Sep 17 00:00:00 2001 From: wapeety Date: Tue, 14 Nov 2023 17:54:15 +0100 Subject: [PATCH 08/12] Adding account, broken :) --- app/src/main/AndroidManifest.xml | 6 ++- .../java/org/epic_guys/esse4/API/API.java | 16 +++++-- .../esse4/activities/LoginActivity.java | 44 ++++++++++++++++--- .../esse4/activities/MainActivity.java | 23 ++++++---- 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index efb9c10..b2d03bb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,12 @@ + + + - call, @NotNull Response response) // API.fetchJwk().join(); //log data to console - Log.i("API_TAG", response.toString()); + Log.i("API_TAG", BuildConfig.DEBUG ? response.toString() : "Login successful"); API.getInstance().jwt = response.body(); - API.getInstance().isLogged = true; + API.isLogged = true; + Log.i("API_TAG", API.isLogged ? "Logged in" : "Not logged in"); + + } else { API.getInstance().isLogged = false; } @@ -163,7 +171,7 @@ public static CompletableFuture getBasicData(){ public void onResponse(@NotNull Call> call, @NotNull Response> response) { if (response.code() == 200) { //log data to console - Log.i("API_TAG", response.toString()); + Log.i("API_TAG", BuildConfig.DEBUG ? response.toString() : "Anagrafiche Service Fetch successful"); //here I take the data I need, so: // Matricola = { user{ userId } } diff --git a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java index 3349986..fcec2d1 100644 --- a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java +++ b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java @@ -1,6 +1,10 @@ package org.epic_guys.esse4.activities; import androidx.appcompat.app.AppCompatActivity; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Intent; import android.os.Bundle; import android.widget.Button; import android.widget.EditText; @@ -11,6 +15,8 @@ import org.epic_guys.esse4.API.API; +import android.util.Log; + public class LoginActivity extends AppCompatActivity { @@ -37,11 +43,39 @@ protected void onCreate(Bundle savedInstanceState) { throw new RuntimeException("Failed to login"); } }).thenAccept(persona -> { - TextView fullname_view = findViewById(R.id.text_fullname); - fullname_view.setText(persona.getNome()); - runOnUiThread(() -> - Toast.makeText(getApplicationContext(), "Login effettuato", Toast.LENGTH_SHORT).show() - ); + runOnUiThread(() -> { + Toast.makeText(getApplicationContext(), "Login effettuato", Toast.LENGTH_SHORT).show(); + Log.i("LoginActivity", "Login effettuato"); + + Account a = new Account(matricola, "org.epic_guys.esse4"); + + Bundle usrData = new Bundle(); + + AccountManager am = AccountManager.get(this); + Log.i("LoginActivity", "AccountManager: " + am.toString()); + + Log.i("LoginActivity", "Adding account"); + + if(am.addAccountExplicitly( + a, + password, + usrData + )){ + Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, matricola); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, "org.epic_guys.esse4"); + } + + Log.i("LoginActivity", "Account: " + a.toString()); + + Intent mainActivity = new Intent(this, MainActivity.class); + startActivity(mainActivity); + Log.i("LoginActivity", "Starting MainActivity, finishing LoginActivity"); + finish(); + }); + + //TextView fullname_view = findViewById(R.id.text_fullname); + //fullname_view.setText(persona.getNome()); }); }); diff --git a/app/src/main/java/org/epic_guys/esse4/activities/MainActivity.java b/app/src/main/java/org/epic_guys/esse4/activities/MainActivity.java index 1972a5f..92769f3 100644 --- a/app/src/main/java/org/epic_guys/esse4/activities/MainActivity.java +++ b/app/src/main/java/org/epic_guys/esse4/activities/MainActivity.java @@ -4,8 +4,12 @@ import android.accounts.AccountManager; import android.content.Intent; import android.os.Bundle; +import android.util.Log; + import androidx.appcompat.app.AppCompatActivity; +import org.epic_guys.esse4.API.API; + import org.epic_guys.esse4.R; public class MainActivity extends AppCompatActivity { @@ -20,14 +24,17 @@ private void launchLoginActivity() { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - AccountManager am = AccountManager.get(this); - Account[] accounts = am.getAccountsByType("org.epic_guys.esse4"); - if (accounts.length == 0) { - launchLoginActivity(); - } else { - // Manage account, get token, etc. - // if login fails, delete account and return to login activity - } + AccountManager am = AccountManager.get(this); + Account[] accounts = am.getAccountsByType("org.epic_guys.esse4"); + Log.i("MainActivity", "Accounts: " + accounts.length); + if (accounts.length == 0) { + Log.i("MainActivity", "No accounts found"); + launchLoginActivity(); + } else { + Log.i("MainActivity", "Account found"); + // Manage account, get token, etc. + // if login fails, delete account and return to login activity + } setContentView(R.layout.activity_main); From f165f7292aa5fd8b82edcb65b16d5193ce2c8d47 Mon Sep 17 00:00:00 2001 From: Alvise Favero Date: Tue, 14 Nov 2023 17:56:53 +0100 Subject: [PATCH 09/12] The test --- app/src/test/java/org/epic_guys/esse4/RetrofitTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java b/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java index ab840c5..d70485b 100644 --- a/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java +++ b/app/src/test/java/org/epic_guys/esse4/RetrofitTest.java @@ -1,6 +1,7 @@ package org.epic_guys.esse4; import org.epic_guys.esse4.API.services.AnagraficheService; +import org.epic_guys.esse4.models.Jwt; import org.epic_guys.esse4.models.Persona; import okhttp3.*; import org.junit.Test; @@ -44,4 +45,10 @@ public void testPersona() throws IOException { // System.out.println(p.getNome() + " " + p.getCognome()); // } } + + @Test + public void testJwt() { + Jwt jwt = new Jwt("eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6ImVzc2UzIn0.eyJzdWIiOiI4ODg4NTEiLCJwcm9maWxlIjoiU1RVREVOVEUiLCJmaXNjYWxDb2RlIjoiRlZSTFZTMDJSMDZEMzI1QyIsImlzcyI6ImVzc2UzIiwiZXhwIjoxNjk5OTgxMzQ5LCJpYXQiOjE2OTk5ODA0NDksInRlbmFudCI6IlVOSVZFIn0.skW3sEJ1CnG3fLmp8saCKKP621DOXFXX3pUymXiD4s6LP7zpl4pzVlZpTA6REP8on5zvplt03bHIOzLHYxqaejtlE8hv8IuoJn1iYP6pmsBCzCxetghYSjal6GUHMJI9iiuoLtXY1hBFCNyuuZTA6-fK1nXaP9xgHcGAzL2MuC4dMEaBjYV9wHds2z6-GsbNOaCYLX50FPlmEPQtnLfCxOeYqNy_pSTIDlZIzuWuPw5mT0vqK_5kTPwsYhFOnlqUKPVik62idPxt3uCT1NO4M0UXZLfDbFHPx88-8QLCQo7OLrvc1AWsLogHhIkMb7FmU8MzPNI3L08_WZOlt8Tk6Q"); + System.out.println(jwt.getPayload().getExpirationTime()); + } } From a2ae15a59e72edbd41de3761f679f6614dc7d287 Mon Sep 17 00:00:00 2001 From: wapeety Date: Wed, 15 Nov 2023 14:52:34 +0100 Subject: [PATCH 10/12] Adding account, broken (added some logs) --- .../java/org/epic_guys/esse4/API/API.java | 2 ++ .../esse4/activities/LoginActivity.java | 21 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/epic_guys/esse4/API/API.java b/app/src/main/java/org/epic_guys/esse4/API/API.java index 03ee30f..08b1fbf 100644 --- a/app/src/main/java/org/epic_guys/esse4/API/API.java +++ b/app/src/main/java/org/epic_guys/esse4/API/API.java @@ -11,6 +11,8 @@ import okhttp3.OkHttpClient; import okhttp3.Credentials; import okhttp3.Request; + +import org.conscrypt.BuildConfig; import org.epic_guys.esse4.API.services.AnagraficheService; import org.epic_guys.esse4.API.services.ApiService; import org.epic_guys.esse4.API.services.JwtService; diff --git a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java index fcec2d1..2a9fd2f 100644 --- a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java +++ b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java @@ -44,9 +44,9 @@ protected void onCreate(Bundle savedInstanceState) { } }).thenAccept(persona -> { runOnUiThread(() -> { - Toast.makeText(getApplicationContext(), "Login effettuato", Toast.LENGTH_SHORT).show(); - Log.i("LoginActivity", "Login effettuato"); - + Toast.makeText(getApplicationContext(), "Login effettuato", Toast.LENGTH_SHORT).show(); + Log.i("LoginActivity", "Login effettuato"); + }); Account a = new Account(matricola, "org.epic_guys.esse4"); Bundle usrData = new Bundle(); @@ -56,6 +56,19 @@ protected void onCreate(Bundle savedInstanceState) { Log.i("LoginActivity", "Adding account"); + am.addAccountExplicitly( + a, + password, + usrData + );// why this operation blocks everything? + + /* + 2023-11-15 14:40:35.265 7181-7191 epic_guys.esse4 org.epic_guys.esse4 W Cleared Reference was only reachable from finalizer (only reported once) + 2023-11-15 14:45:27.329 7377-7388 System org.epic_guys.esse4 W A resource failed to call close. + */ + + Log.i("LoginActivity", "Account added"); + if(am.addAccountExplicitly( a, password, @@ -72,7 +85,7 @@ protected void onCreate(Bundle savedInstanceState) { startActivity(mainActivity); Log.i("LoginActivity", "Starting MainActivity, finishing LoginActivity"); finish(); - }); + //}); //TextView fullname_view = findViewById(R.id.text_fullname); //fullname_view.setText(persona.getNome()); From 9d8f98b535b9eaabef31d3f1a4a5eeac2072b0dd Mon Sep 17 00:00:00 2001 From: wapeety Date: Thu, 16 Nov 2023 14:49:14 +0100 Subject: [PATCH 11/12] Removed account, using secure storage, unable to compile for gradle issues :( --- app/build.gradle | 3 +- .../esse4/activities/LoginActivity.java | 50 +++++-------------- .../esse4/activities/MainActivity.java | 34 +++++++++---- 3 files changed, 38 insertions(+), 49 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d00a62a..9c0bdee 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,7 +47,8 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.12.3' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' runtimeOnly 'io.jsonwebtoken:jjwt-gson:0.12.3' - + //Secure storage + implementation 'de.adorsys.android:securestoragelibrary:' //unable to find artifact, fix later implementation 'com.google.code.gson:gson:2.10.1' testImplementation 'junit:junit:4.13.2' diff --git a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java index 2a9fd2f..7e7ccd7 100644 --- a/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java +++ b/app/src/main/java/org/epic_guys/esse4/activities/LoginActivity.java @@ -47,48 +47,22 @@ protected void onCreate(Bundle savedInstanceState) { Toast.makeText(getApplicationContext(), "Login effettuato", Toast.LENGTH_SHORT).show(); Log.i("LoginActivity", "Login effettuato"); }); - Account a = new Account(matricola, "org.epic_guys.esse4"); - Bundle usrData = new Bundle(); - - AccountManager am = AccountManager.get(this); - Log.i("LoginActivity", "AccountManager: " + am.toString()); - - Log.i("LoginActivity", "Adding account"); - - am.addAccountExplicitly( - a, - password, - usrData - );// why this operation blocks everything? - - /* - 2023-11-15 14:40:35.265 7181-7191 epic_guys.esse4 org.epic_guys.esse4 W Cleared Reference was only reachable from finalizer (only reported once) - 2023-11-15 14:45:27.329 7377-7388 System org.epic_guys.esse4 W A resource failed to call close. - */ + try { + SecurePreferences.setValue(this, "matricola", matricola); + SecurePreferences.setValue(this, "password", password); Log.i("LoginActivity", "Account added"); + } + catch (Exception e) { + Log.e("LoginActivity", "Failed to save credentials"); + } + - if(am.addAccountExplicitly( - a, - password, - usrData - )){ - Bundle result = new Bundle(); - result.putString(AccountManager.KEY_ACCOUNT_NAME, matricola); - result.putString(AccountManager.KEY_ACCOUNT_TYPE, "org.epic_guys.esse4"); - } - - Log.i("LoginActivity", "Account: " + a.toString()); - - Intent mainActivity = new Intent(this, MainActivity.class); - startActivity(mainActivity); - Log.i("LoginActivity", "Starting MainActivity, finishing LoginActivity"); - finish(); - //}); - - //TextView fullname_view = findViewById(R.id.text_fullname); - //fullname_view.setText(persona.getNome()); + Intent mainActivity = new Intent(this, MainActivity.class); + startActivity(mainActivity); + Log.i("LoginActivity", "Starting MainActivity, finishing LoginActivity"); + finish(); }); }); diff --git a/app/src/main/java/org/epic_guys/esse4/activities/MainActivity.java b/app/src/main/java/org/epic_guys/esse4/activities/MainActivity.java index 92769f3..d1ae960 100644 --- a/app/src/main/java/org/epic_guys/esse4/activities/MainActivity.java +++ b/app/src/main/java/org/epic_guys/esse4/activities/MainActivity.java @@ -24,17 +24,31 @@ private void launchLoginActivity() { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - AccountManager am = AccountManager.get(this); - Account[] accounts = am.getAccountsByType("org.epic_guys.esse4"); - Log.i("MainActivity", "Accounts: " + accounts.length); - if (accounts.length == 0) { - Log.i("MainActivity", "No accounts found"); + // check if secure preferences are set, if not launch login activity + try{ + String matricola = SecurePreferences.getStringValue(this, "matricola", null); + String password = SecurePreferences.getStringValue(this, "password", null); + + if (matricola == null || password == null) launchLoginActivity(); - } else { - Log.i("MainActivity", "Account found"); - // Manage account, get token, etc. - // if login fails, delete account and return to login activity - } + + // try to login with saved credentials + API.login(matricola, password).thenComposeAsync(isLogged -> { + if (isLogged) + return API.getBasicData(); + else { + launchLoginActivity(); + throw new RuntimeException("Failed to login"); + } + }).thenAccept(persona -> { + runOnUiThread(() -> { + Log.i("MainActivity", "Login effettuato"); + }); + }); + } + catch (Exception e) { + launchLoginActivity(); + } setContentView(R.layout.activity_main); From 48b517ddc01a86167868ebd372bd0edbbb80c597 Mon Sep 17 00:00:00 2001 From: wapeety Date: Thu, 16 Nov 2023 22:43:43 +0100 Subject: [PATCH 12/12] Secure storage works, implemented basic logout button, defined login flow, ready to merge! --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 2 +- .../esse4/activities/LoginActivity.java | 6 +- .../esse4/activities/MainActivity.java | 59 ++++++++++++++----- app/src/main/res/layout/activity_main.xml | 24 ++++++++ gradle.properties | 3 +- settings.gradle | 1 + 7 files changed, 81 insertions(+), 20 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9c0bdee..3785e65 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,9 +46,11 @@ dependencies { // JWT implementation 'io.jsonwebtoken:jjwt-api:0.12.3' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' - runtimeOnly 'io.jsonwebtoken:jjwt-gson:0.12.3' //Secure storage - implementation 'de.adorsys.android:securestoragelibrary:' //unable to find artifact, fix later + implementation 'com.github.adorsys:secure-storage-android:0.0.2' //unable to find artifact, fix later + + + implementation 'com.google.code.gson:gson:2.10.1' testImplementation 'junit:junit:4.13.2' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b2d03bb..db1a2d7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,7 +8,7 @@ { + Log.i("MainActivity", "Returned to main activity"); + }); // try to login with saved credentials API.login(matricola, password).thenComposeAsync(isLogged -> { if (isLogged) return API.getBasicData(); else { - launchLoginActivity(); throw new RuntimeException("Failed to login"); } }).thenAccept(persona -> { runOnUiThread(() -> { Log.i("MainActivity", "Login effettuato"); + Toast.makeText(this, "Benvenuto " + persona.getNome(), Toast.LENGTH_SHORT).show(); }); + }).exceptionally(e -> { + runOnUiThread(() -> { + Log.i("MainActivity", "Login fallito"); + Toast.makeText(this, "Login fallito", Toast.LENGTH_SHORT).show(); + }); + + launchLoginActivity(); + return null; }); - } - catch (Exception e) { - launchLoginActivity(); - } setContentView(R.layout.activity_main); - // do all the stuff needed to show the main activity + //logout button + findViewById(R.id.btn_logout).setOnClickListener(v -> { + SecurePreferences.setValue("matricola", "", this); + SecurePreferences.setValue("password", "", this); + launchLoginActivity(); + }); + } } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 273ee2c..a25c8d6 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,4 +6,28 @@ android:layout_height="match_parent" tools:context=".activities.MainActivity"> + + +