mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #38 from overleaf/msw-disabled-projects
Report write-latex API 4xx errors in a friendly way to users
This commit is contained in:
commit
3c86eb0d52
10 changed files with 208 additions and 18 deletions
|
@ -35,6 +35,7 @@ import uk.ac.ic.wlgitbridge.git.handler.hook.WriteLatexPutHook;
|
|||
import uk.ac.ic.wlgitbridge.server.FileHandler;
|
||||
import uk.ac.ic.wlgitbridge.server.PostbackContents;
|
||||
import uk.ac.ic.wlgitbridge.server.PostbackHandler;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.base.MissingRepositoryException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.getforversion.SnapshotAttachment;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.push.PostbackManager;
|
||||
|
@ -383,6 +384,7 @@ public class Bridge {
|
|||
* @param hostname
|
||||
* @throws SnapshotPostException
|
||||
* @throws IOException
|
||||
* @throws MissingRepositoryException
|
||||
* @throws ForbiddenException
|
||||
*/
|
||||
public void push(
|
||||
|
@ -391,7 +393,7 @@ public class Bridge {
|
|||
RawDirectory directoryContents,
|
||||
RawDirectory oldDirectoryContents,
|
||||
String hostname
|
||||
) throws SnapshotPostException, IOException, ForbiddenException {
|
||||
) throws SnapshotPostException, IOException, MissingRepositoryException, ForbiddenException {
|
||||
try (LockGuard __ = lock.lockGuard(projectName)) {
|
||||
pushCritical(
|
||||
oauth2,
|
||||
|
@ -460,6 +462,7 @@ public class Bridge {
|
|||
* @param directoryContents
|
||||
* @param oldDirectoryContents
|
||||
* @throws IOException
|
||||
* @throws MissingRepositoryException
|
||||
* @throws ForbiddenException
|
||||
* @throws SnapshotPostException
|
||||
*/
|
||||
|
@ -468,7 +471,7 @@ public class Bridge {
|
|||
String projectName,
|
||||
RawDirectory directoryContents,
|
||||
RawDirectory oldDirectoryContents
|
||||
) throws IOException, ForbiddenException, SnapshotPostException {
|
||||
) throws IOException, MissingRepositoryException, ForbiddenException, SnapshotPostException {
|
||||
Log.info("[{}] Pushing", projectName);
|
||||
String postbackKey = postbackManager.makeKeyForProject(projectName);
|
||||
Log.info(
|
||||
|
|
|
@ -2,6 +2,7 @@ package uk.ac.ic.wlgitbridge.bridge.snapshot;
|
|||
|
||||
import com.google.api.client.auth.oauth2.Credential;
|
||||
import uk.ac.ic.wlgitbridge.data.CandidateSnapshot;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.base.MissingRepositoryException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocResult;
|
||||
|
@ -33,13 +34,14 @@ public interface SnapshotApi {
|
|||
String postbackKey);
|
||||
|
||||
static <T> T getResult(CompletableFuture<T> result)
|
||||
throws FailedConnectionException, ForbiddenException {
|
||||
throws MissingRepositoryException, FailedConnectionException, ForbiddenException {
|
||||
try {
|
||||
return result.join();
|
||||
} catch (CompletionException e) {
|
||||
try {
|
||||
throw e.getCause();
|
||||
} catch (FailedConnectionException
|
||||
} catch (MissingRepositoryException
|
||||
| FailedConnectionException
|
||||
| ForbiddenException
|
||||
| RuntimeException r) {
|
||||
throw r;
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.google.api.client.auth.oauth2.Credential;
|
|||
import uk.ac.ic.wlgitbridge.data.CandidateSnapshot;
|
||||
import uk.ac.ic.wlgitbridge.data.model.Snapshot;
|
||||
import uk.ac.ic.wlgitbridge.git.exception.GitUserException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.base.MissingRepositoryException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocResult;
|
||||
|
@ -65,7 +66,7 @@ public class SnapshotApiFacade {
|
|||
Optional<Credential> oauth2,
|
||||
CandidateSnapshot candidateSnapshot,
|
||||
String postbackKey
|
||||
) throws FailedConnectionException, ForbiddenException {
|
||||
) throws MissingRepositoryException, FailedConnectionException, ForbiddenException {
|
||||
return SnapshotApi.getResult(api.push(
|
||||
oauth2, candidateSnapshot, postbackKey));
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.apache.commons.codec.binary.Base64;
|
|||
import org.eclipse.jetty.server.Request;
|
||||
import uk.ac.ic.wlgitbridge.application.config.Oauth2;
|
||||
import uk.ac.ic.wlgitbridge.bridge.snapshot.SnapshotApi;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.base.MissingRepositoryException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.base.ForbiddenException;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.getdoc.GetDocRequest;
|
||||
import uk.ac.ic.wlgitbridge.util.Instance;
|
||||
|
@ -76,6 +77,8 @@ public class Oauth2Filter implements Filter {
|
|||
filterChain
|
||||
);
|
||||
return;
|
||||
} catch (MissingRepositoryException e) {
|
||||
handleMissingRepository(project, e, (HttpServletResponse) servletResponse);
|
||||
}
|
||||
Log.info("[{}] Auth not needed", project);
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
|
@ -130,7 +133,7 @@ public class Oauth2Filter implements Filter {
|
|||
)
|
||||
).execute().getAccessToken();
|
||||
} catch (TokenResponseException e) {
|
||||
unauthorized(projectName, capturedUsername, e.getStatusCode(), request, response);
|
||||
handleNeedAuthorization(projectName, capturedUsername, e.getStatusCode(), request, response);
|
||||
return;
|
||||
}
|
||||
final Credential cred = new Credential.Builder(
|
||||
|
@ -145,7 +148,7 @@ public class Oauth2Filter implements Filter {
|
|||
servletResponse
|
||||
);
|
||||
} else {
|
||||
unauthorized(projectName, capturedUsername, 0, request, response);
|
||||
handleNeedAuthorization(projectName, capturedUsername, 0, request, response);
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new Error("Couldn't retrieve authentication", e);
|
||||
|
@ -153,14 +156,14 @@ public class Oauth2Filter implements Filter {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
unauthorized(projectName, capturedUsername, 0, request, response);
|
||||
handleNeedAuthorization(projectName, capturedUsername, 0, request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {}
|
||||
|
||||
private void unauthorized(
|
||||
private void handleNeedAuthorization(
|
||||
String projectName,
|
||||
String userName,
|
||||
int statusCode,
|
||||
|
@ -200,4 +203,23 @@ public class Oauth2Filter implements Filter {
|
|||
w.close();
|
||||
}
|
||||
|
||||
private void handleMissingRepository(
|
||||
String projectName,
|
||||
MissingRepositoryException e,
|
||||
HttpServletResponse response
|
||||
) throws IOException {
|
||||
Log.info("[{}] Project missing.", projectName);
|
||||
|
||||
response.setContentType("text/plain");
|
||||
|
||||
// git special-cases 404 to give "repository '%s' not found",
|
||||
// rather than displaying the raw status code.
|
||||
response.setStatus(404);
|
||||
|
||||
PrintWriter w = response.getWriter();
|
||||
for (String line : e.getDescriptionLines()) {
|
||||
w.println(line);
|
||||
}
|
||||
w.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package uk.ac.ic.wlgitbridge.snapshot.base;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import uk.ac.ic.wlgitbridge.git.exception.SnapshotAPIException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MissingRepositoryException extends SnapshotAPIException {
|
||||
|
||||
public static final List<String> GENERIC_REASON = Arrays.asList(
|
||||
"This Overleaf project currently has no git access.",
|
||||
"",
|
||||
"If this problem persists, please contact us."
|
||||
);
|
||||
|
||||
public static final List<String> EXPORTED_TO_V2 = Arrays.asList(
|
||||
"This Overleaf project has been moved to Overleaf v2, and git access is temporarily unsupported.",
|
||||
"",
|
||||
"See https://www.overleaf.com/help/342 for more information."
|
||||
);
|
||||
|
||||
private List<String> descriptionLines;
|
||||
|
||||
public MissingRepositoryException() {
|
||||
descriptionLines = new ArrayList<String>();
|
||||
}
|
||||
|
||||
public MissingRepositoryException(List<String> descriptionLines) {
|
||||
this.descriptionLines = descriptionLines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromJSON(JsonElement json) {}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "no git access";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getDescriptionLines() {
|
||||
return this.descriptionLines;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@ package uk.ac.ic.wlgitbridge.snapshot.base;
|
|||
|
||||
import com.google.api.client.http.*;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.ning.http.client.AsyncHttpClient;
|
||||
import uk.ac.ic.wlgitbridge.snapshot.exception.FailedConnectionException;
|
||||
import uk.ac.ic.wlgitbridge.util.Instance;
|
||||
|
@ -50,7 +51,7 @@ public abstract class Request<T extends Result> {
|
|||
return ret;
|
||||
}
|
||||
|
||||
private T getResult() throws FailedConnectionException, ForbiddenException {
|
||||
private T getResult() throws MissingRepositoryException, FailedConnectionException, ForbiddenException {
|
||||
try {
|
||||
HttpResponse response = future.get();
|
||||
Log.info(
|
||||
|
@ -68,12 +69,30 @@ public abstract class Request<T extends Result> {
|
|||
throw new FailedConnectionException();
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof HttpResponseException &&
|
||||
(((HttpResponseException) cause).getStatusCode() ==
|
||||
HttpServletResponse.SC_UNAUTHORIZED ||
|
||||
((HttpResponseException) cause).getStatusCode() ==
|
||||
HttpServletResponse.SC_FORBIDDEN)) {
|
||||
throw new ForbiddenException();
|
||||
if (cause instanceof HttpResponseException) {
|
||||
HttpResponseException httpCause = (HttpResponseException) cause;
|
||||
int sc = httpCause.getStatusCode();
|
||||
if (sc == HttpServletResponse.SC_UNAUTHORIZED || sc == HttpServletResponse.SC_FORBIDDEN) {
|
||||
throw new ForbiddenException();
|
||||
} else if (sc == HttpServletResponse.SC_NOT_FOUND) {
|
||||
try {
|
||||
JsonObject json = Instance.gson.fromJson(httpCause.getContent(), JsonObject.class);
|
||||
String message = json.get("message").getAsString();
|
||||
|
||||
if ("Exported to v2".equals(message)) {
|
||||
throw new MissingRepositoryException(MissingRepositoryException.EXPORTED_TO_V2);
|
||||
}
|
||||
} catch (IllegalStateException
|
||||
| ClassCastException
|
||||
| NullPointerException _) {
|
||||
// disregard any errors that arose while handling the JSON
|
||||
}
|
||||
|
||||
throw new MissingRepositoryException();
|
||||
} else if (sc >= 400 && sc < 500) {
|
||||
throw new MissingRepositoryException(MissingRepositoryException.GENERIC_REASON);
|
||||
}
|
||||
throw new FailedConnectionException(cause);
|
||||
} else {
|
||||
throw new FailedConnectionException(cause);
|
||||
}
|
||||
|
|
|
@ -42,8 +42,14 @@ public class WLGitBridgeIntegrationTest {
|
|||
put("canCloneMultipleRepositories", new HashMap<String, SnapshotAPIState>() {{
|
||||
put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/canCloneMultipleRepositories/state/state.json")).build());
|
||||
}});
|
||||
put("cannotCloneAProtectedProject", new HashMap<String, SnapshotAPIState>() {{
|
||||
put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/cannotCloneAProtectedProject/state/state.json")).build());
|
||||
put("cannotCloneAProtectedProjectWithoutAuthentication", new HashMap<String, SnapshotAPIState>() {{
|
||||
put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/cannotCloneAProtectedProjectWithoutAuthentication/state/state.json")).build());
|
||||
}});
|
||||
put("cannotCloneA4xxProject", new HashMap<String, SnapshotAPIState>() {{
|
||||
put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/cannotCloneA4xxProject/state/state.json")).build());
|
||||
}});
|
||||
put("cannotCloneAMissingProject", new HashMap<String, SnapshotAPIState>() {{
|
||||
put("state", new SnapshotAPIStateBuilder(getResourceAsStream("/cannotCloneAMissingProject/state/state.json")).build());
|
||||
}});
|
||||
put("canPullAModifiedTexFile", new HashMap<String, SnapshotAPIState>() {{
|
||||
put("base", new SnapshotAPIStateBuilder(getResourceAsStream("/canPullAModifiedTexFile/base/state.json")).build());
|
||||
|
@ -727,6 +733,60 @@ public class WLGitBridgeIntegrationTest {
|
|||
wlgb.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotCloneAProtectedProjectWithoutAuthentication() throws IOException, GitAPIException, InterruptedException {
|
||||
int gitBridgePort = 33883;
|
||||
int mockServerPort = 3883;
|
||||
|
||||
MockSnapshotServer server = new MockSnapshotServer(mockServerPort, getResource("/cannotCloneAProtectedProjectWithoutAuthentication").toFile());
|
||||
server.start();
|
||||
server.setState(states.get("cannotCloneAProtectedProjectWithoutAuthentication").get("state"));
|
||||
GitBridgeApp wlgb = new GitBridgeApp(new String[] {
|
||||
makeConfigFile(gitBridgePort, mockServerPort)
|
||||
});
|
||||
|
||||
wlgb.run();
|
||||
Process gitProcess = runtime.exec("git clone http://127.0.0.1:" + gitBridgePort + "/testproj.git", null, dir);
|
||||
wlgb.stop();
|
||||
assertNotEquals(0, gitProcess.waitFor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotCloneA4xxProject() throws IOException, GitAPIException, InterruptedException {
|
||||
int gitBridgePort = 33879;
|
||||
int mockServerPort = 3879;
|
||||
|
||||
MockSnapshotServer server = new MockSnapshotServer(mockServerPort, getResource("/cannotCloneA4xxProject").toFile());
|
||||
server.start();
|
||||
server.setState(states.get("cannotCloneA4xxProject").get("state"));
|
||||
GitBridgeApp wlgb = new GitBridgeApp(new String[] {
|
||||
makeConfigFile(gitBridgePort, mockServerPort)
|
||||
});
|
||||
|
||||
wlgb.run();
|
||||
Process gitProcess = runtime.exec("git clone http://127.0.0.1:" + gitBridgePort + "/testproj.git", null, dir);
|
||||
wlgb.stop();
|
||||
assertNotEquals(0, gitProcess.waitFor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotCloneAMissingProject() throws IOException, GitAPIException, InterruptedException {
|
||||
int gitBridgePort = 33880;
|
||||
int mockServerPort = 3880;
|
||||
|
||||
MockSnapshotServer server = new MockSnapshotServer(mockServerPort, getResource("/cannotCloneAMissingProject").toFile());
|
||||
server.start();
|
||||
server.setState(states.get("cannotCloneAMissingProject").get("state"));
|
||||
GitBridgeApp wlgb = new GitBridgeApp(new String[] {
|
||||
makeConfigFile(gitBridgePort, mockServerPort)
|
||||
});
|
||||
|
||||
wlgb.run();
|
||||
Process gitProcess = runtime.exec("git clone http://127.0.0.1:" + gitBridgePort + "/testproj.git", null, dir);
|
||||
wlgb.stop();
|
||||
assertNotEquals(0, gitProcess.waitFor());
|
||||
}
|
||||
|
||||
private String makeConfigFile(
|
||||
int port,
|
||||
int apiPort
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
[
|
||||
{
|
||||
"project": "gone",
|
||||
"getDoc": {
|
||||
"error": 410,
|
||||
"versionID": 1,
|
||||
"createdAt": "2018-02-05T15:30:00Z",
|
||||
"email": "michael.walker@overleaf.com",
|
||||
"name": "msw"
|
||||
},
|
||||
"getSavedVers": [],
|
||||
"getForVers": [],
|
||||
"push": "success",
|
||||
"postback": {
|
||||
"type": "outOfDate"
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
[
|
||||
{
|
||||
"project": "missing",
|
||||
"getDoc": {
|
||||
"error": 404,
|
||||
"versionID": 1,
|
||||
"createdAt": "2018-02-06T13:29:00Z",
|
||||
"email": "michael.walker@overleaf.com",
|
||||
"name": "msw"
|
||||
},
|
||||
"getSavedVers": [],
|
||||
"getForVers": [],
|
||||
"push": "success",
|
||||
"postback": {
|
||||
"type": "outOfDate"
|
||||
}
|
||||
}
|
||||
]
|
Loading…
Reference in a new issue