From ec7c196052eb136d2519bc0b3f7612aa335fd1c1 Mon Sep 17 00:00:00 2001 From: Bianca Steffes Date: Mon, 15 Nov 2021 08:12:34 +0100 Subject: [PATCH] Included reidentification on expired access token --- export/src/export/Main.java | 284 +++++++++++++---------- export/src/export/MainTest.java | 30 +-- export/src/export/NoAccessException.java | 7 + export/src/export/Triplet.java | 5 + 4 files changed, 192 insertions(+), 134 deletions(-) create mode 100644 export/src/export/NoAccessException.java diff --git a/export/src/export/Main.java b/export/src/export/Main.java index 91acb64..dc689d5 100644 --- a/export/src/export/Main.java +++ b/export/src/export/Main.java @@ -42,13 +42,18 @@ public class Main private static JSONParser parser = new JSONParser(); private static String testRequest; private static File errorFile; - //TODO: enduco needs to insert the correct request limits here + // TODO: enduco needs to insert the correct request limits here private static final int requestLimit15Minutes = 100; private static final int requestLimitDay = 1000 / 3; - private static int dailyRequestCount =0; - private static int waitTimeMil = 60000*3*15/requestLimit15Minutes; - private static long lastRequestTimeInMillis=0; + private static int dailyRequestCount = 0; + private static int waitTimeMil = 60000 * 3 * 15 / requestLimit15Minutes; + private static long lastRequestTimeInMillis = 0; + private static String accessToken = ""; + /** + * a is client_id, b is client_secret, c is refresh_token + */ + private static Triplet refreshInfo; private static void writeError(String text) { @@ -80,32 +85,50 @@ public class Main /** * Saves the data of one athlete into a temp file and returns it. * - * @param token Identifier / authorization token of the athlete * @return created temp file */ @SuppressWarnings("unchecked") - static File oneAthlete(String token) + static File oneAthlete() { - // get Activities - Map activities = getActivities(token); + // get Activities and general information + Map activities; + JSONObject athleteInfo; + try + { + activities = getActivities(); + athleteInfo = saveGeneralInformation(); + } + catch (NoAccessException e1) + { + writeError("Athlete " + athleteId + ": Access expired and no new token possible"); + return null; // no data at all. Stop right away + } + if (athleteInfo == null) // error getting General Information + { + athleteInfo = new JSONObject(); + } + // for each activity: save streams JSONArray allActivities = new JSONArray(); int simpleActivityId = 0; for (String id : activities.keySet()) { - JSONObject data = addStreams(id, activities.get(id), token); + JSONObject data; + try + { + data = addStreams(id, activities.get(id)); + } + catch (NoAccessException e) + { + writeError("Athlete " + athleteId + ": Access expired and no new token possible"); + break; //stop the loop and save what you got up to there + } data.put("activity_id", simpleActivityId); allActivities.add(data); simpleActivityId++; } - // get general information - JSONObject athleteInfo = saveGeneralInformation(token); - if(athleteInfo==null) //error getting General Information - { - athleteInfo = new JSONObject(); - } athleteInfo.put("activities", allActivities); try @@ -127,21 +150,21 @@ public class Main /** * Adds the streams to the given activity * - * @param id Strava id of the activity - * @param data general information of the activity - * @param token Identifier / authorization token of the athlete + * @param id Strava id of the activity + * @param data general information of the activity * @return The data with the added streams + * @throws NoAccessException If the access token expired and no new one could be acquired. */ @SuppressWarnings("unchecked") - static JSONObject addStreams(String id, JSONObject data, String token) + static JSONObject addStreams(String id, JSONObject data) throws NoAccessException { String requestUrlExtension = "activities/" + id + "/streams?" + "keys=[time,distance,latlng,altitude,velocity_smooth,heartrate," + "cadence,watts,temp,moving,grade_smooth]&key_by_type=true"; - - String json = makeGetRequestWithRetry(requestUrlExtension, token);; - if (json==null ||json.isEmpty()||json.isBlank()||json.equals("")) + String json = makeGetRequestWithRetry(requestUrlExtension); + + if (json == null || json.isEmpty() || json.isBlank() || json.equals("")) { return data; } @@ -167,12 +190,12 @@ public class Main * Gathers all activities of a user, extracts the general information and the * ids. * - * @param token Identifier / authorization token of the athlete * @return A Map with key = Strava Id of an activity and value = JSONObject with * the general information of the activity + * @throws NoAccessException If the access token expired and no new one could be acquired. */ @SuppressWarnings("unchecked") - static Map getActivities(String token) + static Map getActivities() throws NoAccessException { Map result = new HashMap<>(); /* @@ -190,11 +213,13 @@ public class Main int pageIndex = 1; while (true) - { - //TODO: this 'per_page' value could perhaps be the root of all evil + { String requestExtension = "athlete/activities?per_page=100&page=" + pageIndex; - String json = makeGetRequestWithRetry(requestExtension, token); - if (json==null || json.isEmpty() || json.isBlank() || json.equals("") || json.equals("[]")) // don't know where the last page is... + String json = makeGetRequestWithRetry(requestExtension); + if (json == null || json.isEmpty() || json.isBlank() || json.equals("") || json.equals("[]")) // don't know + // where the + // last page + // is... { break; } @@ -231,16 +256,17 @@ public class Main } /** - * In the case that the daily request limit has been reached, this method waits for the next day + * In the case that the daily request limit has been reached, this method waits + * for the next day */ static void checkRequestLimit() { - if(dailyRequestCount>requestLimitDay) //daily request limit reached, wait until the next day + if (dailyRequestCount > requestLimitDay) // daily request limit reached, wait until the next day { Calendar tomorrow = new GregorianCalendar(); tomorrow.setTimeInMillis(System.currentTimeMillis()); - tomorrow.set(Calendar.DAY_OF_YEAR, tomorrow.get(Calendar.DAY_OF_YEAR)+1); - if(tomorrow.get(Calendar.DAY_OF_YEAR)==1) //reached a new year ... + tomorrow.set(Calendar.DAY_OF_YEAR, tomorrow.get(Calendar.DAY_OF_YEAR) + 1); + if (tomorrow.get(Calendar.DAY_OF_YEAR) == 1) // reached a new year ... { tomorrow.set(Calendar.YEAR, 2022); } @@ -248,51 +274,60 @@ public class Main tomorrow.set(Calendar.MINUTE, 0); try { - TimeUnit.MILLISECONDS.sleep(tomorrow.getTimeInMillis()-System.currentTimeMillis()); + TimeUnit.MILLISECONDS.sleep(tomorrow.getTimeInMillis() - System.currentTimeMillis()); } catch (InterruptedException e1) { } - } + } } - + /** - * Method is used to find the next request window: It tries the same request again after 5 minutes. - * After a set number of times (retryTimes) it stops if it still wasn't successful. + * Method is used to find the next request window: It tries the same request + * again after 5 minutes. After a set number of times (retryTimes) it stops if + * it still wasn't successful. + * * @param urlExtension UrlExtension for the request - * @param token Token for the request - * @return Data as a String or {@code null} if there is no data + * @return Data as a String or {@code null} if there is no data + * @throws NoAccessException If the access token expired and no new one could be acquired. */ - static String makeGetRequestWithRetry(String urlExtension, String token) + static String makeGetRequestWithRetry(String urlExtension) throws NoAccessException { checkRequestLimit(); - String json=null; - int count=0; + String json = null; + int count = 0; do { try { - json = makeOneGetRequest(urlExtension, token); + json = makeOneGetRequest(urlExtension); dailyRequestCount++; } catch (ResponseCodeWrongException e) { - //tried enough times, so stop now - if(count >= retryTimes) + // tried enough times, so stop now + if (count >= retryTimes) { - writeError("Athlete: "+athleteId+" Retry limit reached. Last error code: "+e.getResponseCode()); + writeError( + "Athlete: " + athleteId + " Retry limit reached. Last error code: " + e.getResponseCode()); return null; } - //request limit is reached, try again later - if (e.getResponseCode()==httpCodeLimitReached) - { - count++; + if (e.getResponseCode()==HttpURLConnection.HTTP_UNAUTHORIZED) + { //token might have expired + if(!getAccessToken()) //token doesn't work anymore and we can't get a new one + { + throw new NoAccessException(); + } + } + else if (e.getResponseCode() == httpCodeLimitReached) + {// request limit is reached, try again later + count++; } - else //some other error: try only one other time! + else // some other error: try only one other time! { - count=retryTimes; + count = retryTimes; } - //Sleep for 5 minutes and try to get the next 15 min request window + // Sleep for 5 minutes and try to get the next 15 min request window try { TimeUnit.MINUTES.sleep(5); @@ -301,19 +336,19 @@ public class Main { } } - - }while(json==null); + + } while (json == null); return json; } - + /** * Extracts an athletes general information. * - * @param token Identifier / authorization token of the athlete * @return extracted data or null if there was an error + * @throws NoAccessException If the access token expired and no new one could be acquired. */ @SuppressWarnings("unchecked") - static JSONObject saveGeneralInformation(String token) + static JSONObject saveGeneralInformation() throws NoAccessException { String sex = "sex"; // Possible values = M, F String country = "country"; @@ -321,8 +356,7 @@ public class Main String meas_pref = "measurement_preference"; // Possible values = feet, meters String weight = "weight"; - - String json = makeGetRequestWithRetry("athlete", token); + String json = makeGetRequestWithRetry("athlete"); JSONObject toSave = new JSONObject(); try @@ -346,7 +380,7 @@ public class Main catch (NullPointerException e) { writeError("Athlete " + athleteId + ": No general information found."); - return null; + return null; } } @@ -354,12 +388,12 @@ public class Main * Zip a list of files in one .zip file. * * @param files HasMap of which should be zipped - * @param count COunt or id to create distinct files each time + * @param count COunt or id to create distinct files each time * @throws IOException If there was an error zipping */ static void zipFiles(Map files, int count) throws IOException { - FileOutputStream fos = new FileOutputStream("data("+count+").zip"); + FileOutputStream fos = new FileOutputStream("data(" + count + ").zip"); ZipOutputStream zipOut = new ZipOutputStream(fos); for (String key : files.keySet()) { @@ -384,12 +418,10 @@ public class Main * Handles one GET request to the API * * @param requestUrlExtension Extension for the baseUrl (without '/') - * @param token Identification / authorization token of the - * athlete * @return The response as a String, an empty String in case of error. * @throws ResponseCodeWrongException If there was an http error */ - static String makeOneGetRequest(String requestUrlExtension, String token) throws ResponseCodeWrongException + static String makeOneGetRequest(String requestUrlExtension) throws ResponseCodeWrongException { if (testRequest != null) { @@ -400,29 +432,30 @@ public class Main HttpsURLConnection connection = null; try { - long timeSinceLastRequest =System.currentTimeMillis()-lastRequestTimeInMillis; - if( timeSinceLastRequest params = new LinkedHashMap<>(); - params.put("client_id", data.getA()); - params.put("client_secret", data.getB()); + params.put("client_id", refreshInfo.getA()); + params.put("client_secret", refreshInfo.getB()); params.put("grant_type", "refresh_token"); - params.put("refresh_token", data.getC()); + params.put("refresh_token", refreshInfo.getC()); StringBuilder postData = new StringBuilder(); for (String key : params.keySet()) @@ -510,45 +542,57 @@ public class Main } catch (IOException e) { - writeError("Athlete: "+athleteId+"Error while handling POST request: " + e.toString()); + writeError("Athlete: " + athleteId + "Error while handling POST request: " + e.toString()); } return ""; } /** - * Sends the old refresh token to strava and retrieves a new one and an access token - * @param refreshInfo Refresh data with a the cliend_id, b the client_secret and c the refresh_token - * @return A tuple with a the access_token and b the new refresh_token or {@code null} if there was an error + * Sends the old refresh token to strava and retrieves a new one and an access + * token + * + * @param refreshInfo Refresh data with a the cliend_id, b the client_secret and + * c the refresh_token + * @return {@code true} if everything went right or {@code false} if there was + * an error */ - static Tuple getAccessToken(Triplet refreshInfo) + static boolean getAccessToken() { checkRequestLimit(); - String json=null; - int count =0; + String json = null; + int count = 0; do { try { - json = makeOnePostRequest(refreshInfo); + json = makeOnePostRequest(); dailyRequestCount++; } catch (ResponseCodeWrongException e) { - //tried enough times, so stop now - if(count >= retryTimes) + // tried enough times, so stop now + if (count >= retryTimes) { - return null; + return false; + } + + if (e.getResponseCode()==HttpURLConnection.HTTP_UNAUTHORIZED) + { //token might have expired + if(!getAccessToken()) //token doesn't work anymore and we can't get a new one + { + return false; + } } - //request limit is reached, try again later - if (e.getResponseCode()==httpCodeLimitReached) + else if (e.getResponseCode() == httpCodeLimitReached) { - count++; + // request limit is reached, try again later + count++; } - else //some other error: try only one other time! + else // some other error: try only one other time! { - count=retryTimes; + count = retryTimes; } - //Sleep for 5 minutes and try to get the next 15 min request window + // Sleep for 5 minutes and try to get the next 15 min request window try { TimeUnit.MINUTES.sleep(5); @@ -557,19 +601,21 @@ public class Main { } } - - }while(json==null); + + } while (json == null); try { Object obj = parser.parse(json); JSONObject data = (JSONObject) obj; - return new Tuple(data.get("access_token").toString(), data.get("refresh_token").toString()); + accessToken = data.get("access_token").toString(); + refreshInfo.setC(data.get("refresh_token").toString()); + return true; } catch (ParseException e) { - writeError("Athlete " + athleteId + ": Error parsing general information."); + writeError("Athlete " + athleteId + ": Error parsing refresh info."); } - return null; + return false; } public static void main(String[] args) @@ -583,40 +629,40 @@ public class Main List newRefreshTokens = new ArrayList<>(); Map allFiles = new HashMap<>(); - int zipcount =1; + int zipcount = 1; for (Triplet oneUser : refreshTokens) { + refreshInfo = oneUser; athleteId++; - // a is access_token and b is new refresh_token - Tuple withAccessToken = getAccessToken(oneUser); - if (withAccessToken == null) + if (!getAccessToken()) { - writeError("Couldn't get new access token for client "+athleteId); + writeError("Couldn't get new access token for client " + athleteId); continue; } - newRefreshTokens.add(new Triplet(oneUser.getA(), oneUser.getB(), withAccessToken.getB())); - File athlete = oneAthlete(withAccessToken.getA()); + File athlete = oneAthlete(); if (athlete != null) { allFiles.put("Athlete_" + athleteId + ".json", athlete); } - //pack zip-files of 10 athletes - if(allFiles.size()>=10) + newRefreshTokens.add(refreshInfo); + + // pack zip-files of 10 athletes + if (allFiles.size() >= 10) { try { zipFiles(allFiles, zipcount); zipcount++; - allFiles=new HashMap<>(); + allFiles = new HashMap<>(); } catch (IOException e) { writeError("Files coulnd't be zipped"); - } + } } } - //zip the rest + // zip the rest try { zipFiles(allFiles, zipcount); diff --git a/export/src/export/MainTest.java b/export/src/export/MainTest.java index 5bc61f6..a645ccb 100644 --- a/export/src/export/MainTest.java +++ b/export/src/export/MainTest.java @@ -19,7 +19,7 @@ public class MainTest //https://developers.strava.com/docs/reference/ @Test - public void testAddStreams() + public void testAddStreams() throws NoAccessException { String requestDistance = "[ { \"type\" : \"distance\", " + "\"data\" : [ 2.9, 5.8, 8.5, 11.7, 15, 19, 23.2, 28, 32.8, 38.1, 43.8, 49.5 ], " @@ -34,13 +34,13 @@ public class MainTest Main.setTest(requestDistance); - JSONObject res = Main.addStreams("id", new JSONObject(), "token"); + JSONObject res = Main.addStreams("id", new JSONObject()); Assert.assertNull(res.get("stream_time")); Assert.assertNotNull(res.get("stream_distance")); Assert.assertNull(res.get("stream_altitude")); Main.setTest(requestDistanceAndTime); - res = Main.addStreams("id", new JSONObject(), "token"); + res = Main.addStreams("id", new JSONObject()); Assert.assertNotNull(res.get("stream_time")); Assert.assertNotNull(res.get("stream_distance")); Assert.assertNull(res.get("stream_altitude")); @@ -49,7 +49,7 @@ public class MainTest } @Test - public void testGetActivities() + public void testGetActivities() throws NoAccessException { String request_full = "[{\"resource_state\": 2, " + "\"athlete\": {\"id\": 134815, \"resource_state\": 1}, " @@ -124,25 +124,25 @@ public class MainTest for (String request: requestsTwoActivities) { Main.setTest(request); - Map res = Main.getActivities("token"); + Map res = Main.getActivities(); Assert.assertEquals(res.size(), 2); } for (String request: requestsOneActivity) { Main.setTest(request); - Map res = Main.getActivities("token"); + Map res = Main.getActivities(); Assert.assertEquals(res.size(), 1); } Main.setTest(request_missing_id); - Map res = Main.getActivities("token"); + Map res = Main.getActivities(); Assert.assertEquals(res.size(), 0); } @Test - public void testSaveGeneralInformation() + public void testSaveGeneralInformation() throws NoAccessException { String request_full ="{ \"id\" : 1234567890987654321, \"username\" : \"marianne_t\", \"resource_state\" : 3," + "\"firstname\" : \"Marianne\", \"lastname\" : \"Teutenberg\", \"city\" : \"San Francisco\", " @@ -187,26 +187,26 @@ public class MainTest for (String request: allRequests) { Main.setTest(request); - JSONObject o = Main.saveGeneralInformation("token"); + JSONObject o = Main.saveGeneralInformation(); Assert.assertNotNull(o); } Main.setTest(request_bad_format); - JSONObject o = Main.saveGeneralInformation("token"); + JSONObject o = Main.saveGeneralInformation(); Assert.assertNull(o); } @SuppressWarnings("unchecked") @Test - public void testStructure() + public void testStructure() throws NoAccessException { String request_shortened = "[{\"type\": \"Ride\", \"id\": 154504250376823, " + "\"start_date_local\": \"2018-05-02T05:15:09Z\", " + "\"timezone\": \"(GMT-08:00) America/Los_Angeles\"}]"; Main.setTest(request_shortened); - Map res = Main.getActivities("token"); + Map res = Main.getActivities(); String requestDistanceAndTime = "[{\"type\" : \"distance\", " + "\"data\" : [ 2.9, 5.8, 8.5, 11.7, 15, 19, 23.2, 28, 32.8, 38.1, 43.8, 49.5 ], " @@ -219,7 +219,7 @@ public class MainTest for(String activity : res.keySet()) { Main.setTest(requestDistanceAndTime); - JSONObject toSave = Main.addStreams("id", res.get(activity), "token"); + JSONObject toSave = Main.addStreams("id", res.get(activity)); allActivities.add(toSave); } Assert.assertEquals(allActivities.size(), res.keySet().size()); @@ -228,7 +228,7 @@ public class MainTest + "\"measurement_preference\" : \"feet\", \"weight\" : 0}"; Main.setTest(request_missing_country); - JSONObject a = Main.saveGeneralInformation("token"); + JSONObject a = Main.saveGeneralInformation(); a.put("activities", allActivities); try @@ -241,7 +241,7 @@ public class MainTest Map map = new HashMap<>(); map.put("athlete_0.json", temp); - Main.zipFiles(map); + Main.zipFiles(map,0); } catch (IOException e) diff --git a/export/src/export/NoAccessException.java b/export/src/export/NoAccessException.java new file mode 100644 index 0000000..17e8499 --- /dev/null +++ b/export/src/export/NoAccessException.java @@ -0,0 +1,7 @@ +package export; + +public class NoAccessException extends Exception +{ + private static final long serialVersionUID = -3852341856319438435L; + +} diff --git a/export/src/export/Triplet.java b/export/src/export/Triplet.java index 7bc837b..7645a5c 100644 --- a/export/src/export/Triplet.java +++ b/export/src/export/Triplet.java @@ -14,4 +14,9 @@ public class Triplet extends Tuple { return c; } + + public void setC(String c) + { + this.c=c; + } }