Merge pull request #4725 from nextcloud/ssoHeaders

SSO: return response headers
This commit is contained in:
Tobias Kaminsky 2019-10-28 16:12:32 +01:00 committed by GitHub
commit cabe2fcff0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 245 additions and 2 deletions

View file

@ -1 +1 @@
420
423

View file

@ -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);
}

View file

@ -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());

View file

@ -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();
}
}

View file

@ -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);
}
}