Răsfoiți Sursa

SSO: return response headers
new headers in parallel

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>

tobiasKaminsky 5 ani în urmă
părinte
comite
8e3931edf6

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-420
+423

+ 5 - 0
src/main/aidl/com/nextcloud/android/sso/aidl/IInputStreamService.aidl

@@ -24,4 +24,9 @@ interface IInputStreamService {
                                                               in ParcelFileDescriptor requestBodyParcelFileDescriptor);
 
     ParcelFileDescriptor performNextcloudRequest(in ParcelFileDescriptor input);
+
+    ParcelFileDescriptor performNextcloudRequestAndBodyStreamV2(in ParcelFileDescriptor input,
+                                                                 in ParcelFileDescriptor requestBodyParcelFileDescriptor);
+
+    ParcelFileDescriptor performNextcloudRequestV2(in ParcelFileDescriptor input);
 }

+ 120 - 1
src/main/java/com/nextcloud/android/sso/InputStreamBinder.java

@@ -94,6 +94,7 @@ public class InputStreamBinder extends IInputStreamService.Stub {
     private static final int HTTP_STATUS_CODE_MULTIPLE_CHOICES = 300;
 
     private static final char PATH_SEPARATOR = '/';
+    private static final int ZERO_LENGTH = 0;
     private Context context;
     private UserAccountManager accountManager;
 
@@ -112,6 +113,42 @@ public class InputStreamBinder extends IInputStreamService.Stub {
         return nvp;
     }
 
+    public ParcelFileDescriptor performNextcloudRequestV2(ParcelFileDescriptor input) {
+        return performNextcloudRequestAndBodyStreamV2(input, null);
+    }
+
+    public ParcelFileDescriptor performNextcloudRequestAndBodyStreamV2(
+        ParcelFileDescriptor input,
+        ParcelFileDescriptor requestBodyParcelFileDescriptor) {
+        // read the input
+        final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
+
+        final InputStream requestBodyInputStream = requestBodyParcelFileDescriptor != null ?
+            new ParcelFileDescriptor.AutoCloseInputStream(requestBodyParcelFileDescriptor) : null;
+        Exception exception = null;
+        Response response = new Response();
+
+        try {
+            // Start request and catch exceptions
+            NextcloudRequest request = deserializeObjectAndCloseStream(is);
+            response = processRequestV2(request, requestBodyInputStream);
+        } catch (Exception e) {
+            Log_OC.e(TAG, "Error during Nextcloud request", e);
+            exception = e;
+        }
+
+        try {
+            // Write exception to the stream followed by the actual network stream
+            InputStream exceptionStream = serializeObjectToInputStreamV2(exception, response.getPlainHeadersString());
+            InputStream resultStream = new java.io.SequenceInputStream(exceptionStream, response.getBody());
+
+            return ParcelFileDescriptorUtil.pipeFrom(resultStream, thread -> Log.d(TAG, "Done sending result"));
+        } catch (IOException e) {
+            Log_OC.e(TAG, "Error while sending response back to client app", e);
+        }
+        return null;
+    }
+
     public ParcelFileDescriptor performNextcloudRequest(ParcelFileDescriptor input) {
         return performNextcloudRequestAndBodyStream(input, null);
     }
@@ -128,7 +165,7 @@ public class InputStreamBinder extends IInputStreamService.Stub {
         InputStream httpStream = new InputStream() {
             @Override
             public int read() {
-                return 0;
+                return ZERO_LENGTH;
             }
         };
 
@@ -157,6 +194,23 @@ public class InputStreamBinder extends IInputStreamService.Stub {
         return null;
     }
 
+    private ByteArrayInputStream serializeObjectToInputStreamV2(Exception exception, String headers) {
+        byte[] baosByteArray = new byte[0];
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
+            oos.writeObject(exception);
+            oos.writeObject(headers);
+            oos.flush();
+            oos.close();
+
+            baosByteArray = baos.toByteArray();
+        } catch (IOException e) {
+            Log_OC.e(TAG, "Error while sending response back to client app", e);
+        }
+
+        return new ByteArrayInputStream(baosByteArray);
+    }
+
     private <T extends Serializable> ByteArrayInputStream serializeObjectToInputStream(T obj) throws IOException {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(baos);
@@ -315,6 +369,71 @@ public class InputStreamBinder extends IInputStreamService.Stub {
         }
     }
 
+    private Response processRequestV2(final NextcloudRequest request, final InputStream requestBodyInputStream)
+        throws UnsupportedOperationException,
+        com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException,
+        OperationCanceledException, AuthenticatorException, IOException {
+        Account account = accountManager.getAccountByName(request.getAccountName());
+        if (account == null) {
+            throw new IllegalStateException(EXCEPTION_ACCOUNT_NOT_FOUND);
+        }
+
+        // Validate token
+        if (!isValid(request)) {
+            throw new IllegalStateException(EXCEPTION_INVALID_TOKEN);
+        }
+
+        // Validate URL
+        if (request.getUrl().length() == 0 || request.getUrl().charAt(0) != PATH_SEPARATOR) {
+            throw new IllegalStateException(EXCEPTION_INVALID_REQUEST_URL,
+                                            new IllegalStateException("URL need to start with a /"));
+        }
+
+        OwnCloudClientManager ownCloudClientManager = OwnCloudClientManagerFactory.getDefaultSingleton();
+        OwnCloudAccount ocAccount = new OwnCloudAccount(account, context);
+        OwnCloudClient client = ownCloudClientManager.getClientFor(ocAccount, context);
+
+        HttpMethodBase method = buildMethod(request, client.getBaseUri(), requestBodyInputStream);
+
+        method.setQueryString(convertMapToNVP(request.getParameter()));
+        method.addRequestHeader("OCS-APIREQUEST", "true");
+
+        for (Map.Entry<String, List<String>> header : request.getHeader().entrySet()) {
+            // https://stackoverflow.com/a/3097052
+            method.addRequestHeader(header.getKey(), TextUtils.join(",", header.getValue()));
+
+            if ("OCS-APIREQUEST".equalsIgnoreCase(header.getKey())) {
+                throw new IllegalStateException(
+                    "The 'OCS-APIREQUEST' header will be automatically added by the Nextcloud SSO Library. " +
+                        "Please remove the header before making a request");
+            }
+        }
+
+        client.setFollowRedirects(request.isFollowRedirects());
+        int status = client.executeMethod(method);
+
+        // Check if status code is 2xx --> https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success
+        if (status >= HTTP_STATUS_CODE_OK && status < HTTP_STATUS_CODE_MULTIPLE_CHOICES) {
+            return new Response(method.getResponseBodyAsStream(), method.getResponseHeaders());
+        } else {
+            StringBuilder total = new StringBuilder();
+            InputStream inputStream = method.getResponseBodyAsStream();
+            // If response body is available
+            if (inputStream != null) {
+                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+                String line = reader.readLine();
+                while (line != null) {
+                    total.append(line).append('\n');
+                    line = reader.readLine();
+                }
+                Log_OC.e(TAG, total.toString());
+            }
+            throw new IllegalStateException(EXCEPTION_HTTP_REQUEST_FAILED,
+                                            new IllegalStateException(String.valueOf(status),
+                                                                      new IllegalStateException(total.toString())));
+        }
+    }
+
     private boolean isValid(NextcloudRequest request) {
         String callingPackageName = context.getPackageManager().getNameForUid(Binder.getCallingUid());
 

+ 54 - 0
src/main/java/com/nextcloud/android/sso/PlainHeader.java

@@ -0,0 +1,54 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2019 Tobias Kaminsky
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.android.sso;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import lombok.Getter;
+
+public class PlainHeader implements Serializable {
+    private static final long serialVersionUID = 3284979177401282512L;
+
+    @Getter
+    private String name;
+    @Getter
+    private String value;
+
+    PlainHeader(String name, String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    private void writeObject(ObjectOutputStream oos) throws IOException {
+        oos.writeObject(name);
+        oos.writeObject(value);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        name = (String) in.readObject();
+        value = (String) in.readObject();
+    }
+}

+ 65 - 0
src/main/java/com/nextcloud/android/sso/Response.java

@@ -0,0 +1,65 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2019 Tobias Kaminsky
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.android.sso;
+
+import com.google.gson.Gson;
+
+import org.apache.commons.httpclient.Header;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import lombok.Getter;
+
+public class Response {
+    @Getter
+    private InputStream body;
+    private Header[] headers;
+
+    public Response() {
+        headers = new Header[0];
+        body = new InputStream() {
+            @Override
+            public int read() {
+                return 0;
+            }
+        };
+    }
+
+    public Response(InputStream inputStream, Header[] headers) {
+        this.body = inputStream;
+        this.headers = headers;
+    }
+
+    public String getPlainHeadersString() {
+        List<PlainHeader> arrayList = new ArrayList<>(headers.length);
+
+        for (Header header : headers) {
+            arrayList.add(new PlainHeader(header.getName(), header.getValue()));
+        }
+
+        Gson gson = new Gson();
+        return gson.toJson(arrayList);
+    }
+}