InputStreamBinder.java 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package com.nextcloud.android.sso;
  2. import android.accounts.Account;
  3. import android.content.Context;
  4. import android.content.SharedPreferences;
  5. import android.os.Binder;
  6. import android.os.ParcelFileDescriptor;
  7. import android.util.Log;
  8. import com.nextcloud.android.sso.aidl.IInputStreamService;
  9. import com.nextcloud.android.sso.aidl.NextcloudRequest;
  10. import com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil;
  11. import com.owncloud.android.authentication.AccountUtils;
  12. import com.owncloud.android.db.PreferenceManager;
  13. import com.owncloud.android.lib.common.OwnCloudAccount;
  14. import com.owncloud.android.lib.common.OwnCloudClient;
  15. import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
  16. import org.apache.commons.httpclient.HttpMethodBase;
  17. import org.apache.commons.httpclient.NameValuePair;
  18. import org.apache.commons.httpclient.methods.DeleteMethod;
  19. import org.apache.commons.httpclient.methods.GetMethod;
  20. import org.apache.commons.httpclient.methods.PostMethod;
  21. import org.apache.commons.httpclient.methods.PutMethod;
  22. import org.apache.commons.httpclient.methods.StringRequestEntity;
  23. import java.io.ByteArrayInputStream;
  24. import java.io.ByteArrayOutputStream;
  25. import java.io.IOException;
  26. import java.io.InputStream;
  27. import java.io.ObjectInputStream;
  28. import java.io.ObjectOutputStream;
  29. import java.io.Serializable;
  30. import java.util.ArrayList;
  31. import java.util.Arrays;
  32. import java.util.Map;
  33. /**
  34. * Nextcloud SingleSignOn
  35. *
  36. * @author David Luhmer
  37. *
  38. * This program is free software: you can redistribute it and/or modify
  39. * it under the terms of the GNU General Public License as published by
  40. * the Free Software Foundation, either version 3 of the License, or
  41. * (at your option) any later version.
  42. *
  43. * This program is distributed in the hope that it will be useful,
  44. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  45. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  46. * GNU General Public License for more details.
  47. *
  48. * You should have received a copy of the GNU General Public License
  49. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  50. *
  51. *
  52. * More information here: https://github.com/abeluck/android-streams-ipc
  53. *
  54. */
  55. public class InputStreamBinder extends IInputStreamService.Stub {
  56. private final static String TAG = "InputStreamBinder";
  57. private ArrayList<String> validPackages = new ArrayList<>(Arrays.asList("de.luhmer.owncloudnewsreader"));
  58. private Context context;
  59. public InputStreamBinder(Context ctxt) {
  60. this.context = ctxt;
  61. }
  62. private NameValuePair[] convertMapToNVP(Map<String, String> map) {
  63. NameValuePair[] nvp = new NameValuePair[map.size()];
  64. int i = 0;
  65. for (String key : map.keySet()) {
  66. nvp[i] = new NameValuePair(key, map.get(key));
  67. i++;
  68. }
  69. return nvp;
  70. }
  71. public ParcelFileDescriptor performNextcloudRequest(ParcelFileDescriptor input) {
  72. // read the input
  73. final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
  74. Exception exception = null;
  75. InputStream httpStream = new InputStream() {
  76. @Override
  77. public int read() {
  78. return 0;
  79. }
  80. };
  81. try {
  82. // Start request and catch exceptions
  83. NextcloudRequest request = deserializeObjectAndCloseStream(is);
  84. httpStream = processRequest(request);
  85. } catch (Exception e) {
  86. e.printStackTrace();
  87. exception = e;
  88. }
  89. try {
  90. // Write exception to the stream followed by the actual network stream
  91. InputStream exceptionStream = serializeObjectToInputStream(exception);
  92. InputStream resultStream = new java.io.SequenceInputStream(exceptionStream, httpStream);
  93. return ParcelFileDescriptorUtil.pipeFrom(resultStream, thread -> Log.d(TAG, "Done sending result"));
  94. } catch (IOException e) {
  95. e.printStackTrace();
  96. }
  97. return null;
  98. }
  99. private <T extends Serializable> ByteArrayInputStream serializeObjectToInputStream(T obj) throws IOException {
  100. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  101. ObjectOutputStream oos = new ObjectOutputStream(baos);
  102. oos.writeObject(obj);
  103. oos.flush();
  104. oos.close();
  105. return new ByteArrayInputStream(baos.toByteArray());
  106. }
  107. private <T extends Serializable> T deserializeObjectAndCloseStream(InputStream is) throws IOException, ClassNotFoundException {
  108. ObjectInputStream ois = new ObjectInputStream(is);
  109. T result = (T) ois.readObject();
  110. is.close();
  111. ois.close();
  112. return result;
  113. }
  114. private InputStream processRequest(final NextcloudRequest request) throws Exception {
  115. Account account = AccountUtils.getOwnCloudAccountByName(context, request.accountName); // TODO handle case that account is not found!
  116. if(account == null) {
  117. throw new IllegalStateException("CE_2"); // Custom Exception 2 (Account not found)
  118. }
  119. OwnCloudAccount ocAccount = new OwnCloudAccount(account, context);
  120. OwnCloudClient client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, context);
  121. // Validate token & package name
  122. if (!isValid(request)) {
  123. throw new IllegalStateException("CE_1"); // Custom Exception 1 (Invalid token or package name)
  124. }
  125. // Validate URL
  126. if(!request.url.startsWith("/")) {
  127. throw new IllegalStateException("URL need to start with a /");
  128. }
  129. request.url = client.getBaseUri() + request.url;
  130. HttpMethodBase method;
  131. switch (request.method) {
  132. case "GET":
  133. method = new GetMethod(request.url);
  134. break;
  135. case "POST":
  136. method = new PostMethod(request.url);
  137. if (request.requestBody != null) {
  138. StringRequestEntity requestEntity = new StringRequestEntity(
  139. request.requestBody,
  140. "application/json",
  141. "UTF-8");
  142. ((PostMethod) method).setRequestEntity(requestEntity);
  143. }
  144. break;
  145. case "PUT":
  146. method = new PutMethod(request.url);
  147. if (request.requestBody != null) {
  148. StringRequestEntity requestEntity = new StringRequestEntity(
  149. request.requestBody,
  150. "application/json",
  151. "UTF-8");
  152. ((PutMethod) method).setRequestEntity(requestEntity);
  153. }
  154. break;
  155. case "DELETE":
  156. method = new DeleteMethod(request.url);
  157. break;
  158. default:
  159. throw new Exception("Unexpected type!!");
  160. }
  161. method.setQueryString(convertMapToNVP(request.parameter));
  162. method.addRequestHeader("OCS-APIREQUEST", "true");
  163. int status = client.executeMethod(method);
  164. if (status == 200) {
  165. return method.getResponseBodyAsStream();
  166. } else {
  167. throw new Exception("Request returned code: " + status);
  168. }
  169. }
  170. private boolean isValid(NextcloudRequest request) {
  171. if(request.packageName == null) {
  172. String callingPackageName = context.getPackageManager().getNameForUid(Binder.getCallingUid());
  173. request.packageName = callingPackageName;
  174. }
  175. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
  176. String storedToken = sharedPreferences.getString(request.packageName, "");
  177. return validPackages.contains(request.packageName) && request.token.equals(storedToken);
  178. }
  179. }