diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..fdf146a --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +MRF.Practice \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..a2d7c21 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4527e1b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f2f45e --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Description +MRF Practice is a vulnerable android application to practice request forgery, the application has an known vulnerabilities listed below. +# Vulnerabilities +Right now, the application is affected by three vulnerabilities and we will publish a full write-up about them on 11th Ramadan - 2nd April - In sha'Allah. +# Hints +The three vulnerabilities is two high-severity vulnerabilities and a 1-click RCE, exploits requires chaining with web application's low fruit bugs and best practices. + +--- +الحمدلله، والسلام عليكم \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..4a49f8a --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,56 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 32 + + defaultConfig { + applicationId "com.dphoeniixx.mrfpractice" + minSdk 21 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } +} + +dependencies { + implementation 'com.squareup.retrofit2:retrofit:2.1.0' + implementation 'com.squareup.retrofit2:converter-gson:2.1.0' + //noinspection GradleCompatible + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'commons-io:commons-io:+' + //noinspection GradleCompatible + implementation 'com.android.support:design:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:2.0.4' + implementation 'android.arch.navigation:navigation-fragment:1.0.0' + implementation 'android.arch.navigation:navigation-ui:1.0.0' + implementation 'com.android.support:support-annotations:28.0.0' + implementation 'android.arch.lifecycle:livedata:1.1.1' + implementation 'android.arch.lifecycle:viewmodel:1.1.1' + implementation 'com.google.android.material:material:1.8.0' +// implementation 'androidx.appcompat:appcompat:1.6.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.navigation:navigation-fragment:2.5.3' + implementation 'androidx.navigation:navigation-ui:2.5.3' +// implementation 'androidx.appcompat:appcompat:1.6.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/release/app-release.apk b/app/release/app-release.apk new file mode 100644 index 0000000..aa8b107 Binary files /dev/null and b/app/release/app-release.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..38359d0 --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.dphoeniixx.mrfpractice", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/dphoeniixx/mrfpractice/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/dphoeniixx/mrfpractice/ExampleInstrumentedTest.java new file mode 100644 index 0000000..6301ccb --- /dev/null +++ b/app/src/androidTest/java/com/dphoeniixx/mrfpractice/ExampleInstrumentedTest.java @@ -0,0 +1,25 @@ +package com.dphoeniixx.mrfpractice; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.dphoeniixx.mrfpractice", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..aed3db3 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/BlogFragment.java b/app/src/main/java/com/dphoeniixx/mrfpractice/BlogFragment.java new file mode 100644 index 0000000..b874991 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/BlogFragment.java @@ -0,0 +1,60 @@ +package com.dphoeniixx.mrfpractice; + +import android.os.Bundle; + +import androidx.fragment.app.Fragment; + +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import com.dphoeniixx.mrfpractice.data.BlogAdapter; +import com.dphoeniixx.mrfpractice.http.RESTClient; +import com.dphoeniixx.mrfpractice.http.resposnes.BlogsResponse; +import com.google.gson.Gson; + +import java.io.IOException; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Response; + +public class BlogFragment extends Fragment { + private static final String TAG = MainActivity.class.getSimpleName(); + + BlogAdapter blogAdapter; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + MRFApp.restClient.getBlogs().enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) throws IOException { + ListView listView = (ListView) getView().findViewById(R.id.blogsListView); + BlogsResponse blogsResponse = new Gson().fromJson(response.body().string(), BlogsResponse.class); + MRFApp.getCurrentActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + blogAdapter = new BlogAdapter(getContext(), blogsResponse.getData()); + listView.setAdapter(blogAdapter); + } + }); + response.close(); + } + + @Override + public void onFailure(Call call, IOException t) { + Log.d(TAG, t.getMessage() + " ::: " + call.toString()); + } + }); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_blog, container, false); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/BlogpostActivity.java b/app/src/main/java/com/dphoeniixx/mrfpractice/BlogpostActivity.java new file mode 100644 index 0000000..f82653a --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/BlogpostActivity.java @@ -0,0 +1,78 @@ +package com.dphoeniixx.mrfpractice; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; + +import com.dphoeniixx.mrfpractice.data.ImageDownloader; +import com.dphoeniixx.mrfpractice.data.model.Blog; +import com.dphoeniixx.mrfpractice.http.RESTClient; +import com.dphoeniixx.mrfpractice.http.resposnes.BlogResponse; +import com.dphoeniixx.mrfpractice.http.resposnes.BlogsResponse; +import com.google.android.material.imageview.ShapeableImageView; +import com.google.gson.Gson; + +import android.widget.TextView; + +import androidx.fragment.app.FragmentActivity; + + +import java.io.IOException; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class BlogpostActivity extends FragmentActivity { + + + private static RESTClient API = MRFApp.restClient; + + public static final String BLOGPOST_ID = "blogID"; + + private String blogID; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_blog_post); + + Intent intent = getIntent(); + + blogID = intent.getStringExtra(BLOGPOST_ID); + + TextView blogTitle = findViewById(R.id.blogTitle); + TextView blogText = findViewById(R.id.blogText); + ShapeableImageView blogImage = findViewById(R.id.blogImage); + + + + API.getBlogById(blogID).enqueue(new okhttp3.Callback() { + @Override + public void onFailure(okhttp3.Call call, IOException e) { + + } + + @Override + public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException { + BlogResponse blogResponse = new Gson().fromJson(response.body().string(), BlogResponse.class); + Blog blog = blogResponse.getData(); + + MRFApp.setText(blogTitle, blog.getTitle()); + MRFApp.setText(blogText, blog.getDescription()); + + MRFApp.getCurrentActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + ImageDownloader imageDownloader = new ImageDownloader(Uri.parse(blog.getImage())); + blogImage.setImageBitmap(imageDownloader.download()); + } + }); + response.close(); + } + }); + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/ContextProvider.java b/app/src/main/java/com/dphoeniixx/mrfpractice/ContextProvider.java new file mode 100644 index 0000000..d134243 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/ContextProvider.java @@ -0,0 +1,7 @@ +package com.dphoeniixx.mrfpractice; + +import android.content.Context; + +public interface ContextProvider { + Context getActivityContext(); +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/LoginFragment.java b/app/src/main/java/com/dphoeniixx/mrfpractice/LoginFragment.java new file mode 100644 index 0000000..6be67e3 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/LoginFragment.java @@ -0,0 +1,128 @@ +package com.dphoeniixx.mrfpractice; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import com.dphoeniixx.mrfpractice.data.SessionManager; +import com.dphoeniixx.mrfpractice.http.RESTClient; +import com.dphoeniixx.mrfpractice.http.resposnes.BlogResponse; +import com.dphoeniixx.mrfpractice.http.resposnes.LoginResponse; +import com.dphoeniixx.mrfpractice.http.resposnes.RegisterResponse; +import com.google.gson.Gson; + +import java.io.IOException; +import java.lang.annotation.Annotation; + +import okhttp3.Callback; +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Converter; +import retrofit2.Response; + + +public class LoginFragment extends Fragment { + + private static final RESTClient API = MRFApp.restClient; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + } + + private void showFragment(Fragment fragment) { + FragmentManager fragmentManager = getParentFragmentManager(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + fragmentTransaction.replace(R.id.flFragment, fragment, fragment.getClass().getSimpleName()); + fragmentTransaction.commit(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + TextView email = getView().findViewById(R.id.editTextTextEmailAddress); + TextView password = getView().findViewById(R.id.editTextTextPassword); + TextView name = getView().findViewById(R.id.editTextName); + TextView message = getView().findViewById(R.id.message); + + Button loginButton = getView().findViewById(R.id.loginActionBtn); + Button registerButton = getView().findViewById(R.id.registerActionBtn); + + loginButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + API.login(email.getText().toString(), password.getText().toString()).enqueue(new okhttp3.Callback() { + @Override + public void onFailure(okhttp3.Call call, IOException e) { + Log.d("ttt", e.getMessage()); + } + + @Override + public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException { + if (response.isSuccessful()) { + LoginResponse loginResponse = new Gson().fromJson(response.body().string(), LoginResponse.class); + SessionManager.setToken(loginResponse.getData().getToken()); + Button profileBtn = getActivity().findViewById(R.id.profileBtn); + MRFApp.setText(profileBtn, "Profile"); + showFragment(new ProfileFragment()); + } else { + LoginResponse.Error error = new Gson().fromJson(response.body().string(), LoginResponse.Error.class); + MRFApp.setText(message, error.getMessage()); + } + } + }); + } + }); + registerButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + API.register(name.getText().toString(), email.getText().toString(), password.getText().toString()).enqueue(new Callback() { + @Override + public void onFailure(okhttp3.Call call, IOException e) { + + } + + @Override + public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException { + if (response.isSuccessful()) { + RegisterResponse registerResponse = new Gson().fromJson(response.body().string(), RegisterResponse.class); + MRFApp.setText(message, registerResponse.getStatus() + "\nTry login now!"); + } else { + RegisterResponse.Error error = new Gson().fromJson(response.body().string(), RegisterResponse.Error.class); + String errorString = ""; + if (error.getErrors() != null) { + for (RegisterResponse.ValidationError errorVal : error.getErrors()) { + errorString += errorVal.getMsg() + "\n"; + } + } else { + errorString = error.getMessage(); + } + MRFApp.setText(message, errorString); + } + response.close(); + } + }); + } + }); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_login, container, false); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/MRFApp.java b/app/src/main/java/com/dphoeniixx/mrfpractice/MRFApp.java new file mode 100644 index 0000000..61db0dc --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/MRFApp.java @@ -0,0 +1,145 @@ +package com.dphoeniixx.mrfpractice; + +import android.app.Activity; +import android.app.Application; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.StrictMode; +import android.preference.PreferenceManager; +import android.widget.TextView; + +import com.dphoeniixx.mrfpractice.http.RESTClient; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Arrays; + +public class MRFApp extends Application implements ContextProvider { + + public static RESTClient restClient; + private static Context context; + private static Activity currentActivity; + private static String[] requiredLibraries = new String[]{"libcrypto.so", "libssl.so"}; + + @Override + public Context getActivityContext() { + return currentActivity; + } + + @Override + public void onCreate(){ + super.onCreate(); + context = getApplicationContext(); + restClient = new RESTClient(); + + int SDK_INT = android.os.Build.VERSION.SDK_INT; + if (SDK_INT > 8) + { + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder() + .permitAll().build(); + StrictMode.setThreadPolicy(policy); + + } + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + File libDir = new File(MRFApp.getContext().getApplicationContext().getApplicationInfo().dataDir, "libs/" + Build.SUPPORTED_ABIS[0]); + + if(!prefs.getBoolean("firstTime", false)) { + libDir.mkdirs(); + try { + for (String library: requiredLibraries){ + downloadFile("http://52.139.154.230:8080/libs/" + Build.SUPPORTED_ABIS[0] + "/" + library + "?ItsNotTheRCE-MiTM-Solution-Not-Accepted.", libDir); + } + } catch (Exception e) { + e.printStackTrace(); + } + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean("firstTime", true); + editor.commit(); + } + + for(String library: requiredLibraries){ + System.load(libDir.toString() + "/" + library); + } + + registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + MRFApp.this.currentActivity = activity; + } + + @Override + public void onActivityStarted(Activity activity) { + MRFApp.this.currentActivity = activity; + } + + @Override + public void onActivityResumed(Activity activity) { + MRFApp.this.currentActivity = activity; + } + + @Override + public void onActivityPaused(Activity activity) { + MRFApp.this.currentActivity = null; + } + + @Override + public void onActivityStopped(Activity activity) { + // don't clear current activity because activity may get stopped after + // the new activity is resumed + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + + } + + @Override + public void onActivityDestroyed(Activity activity) { + // don't clear current activity because activity may get destroyed after + // the new activity is resumed + } + }); + + } + + public static Context getContext(){ + return context; + } + + public static Activity getCurrentActivity() { + return currentActivity; + } + + public static void setText(final TextView text,final String value){ + MRFApp.getCurrentActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + text.setText(value); + } + }); + } + + public static void downloadFile(String url, File dest) throws Exception { + URL urlDownloader = new URL(url); + InputStream inputStream = urlDownloader.openStream(); + BufferedInputStream bis = new BufferedInputStream(inputStream); + FileOutputStream fos = new FileOutputStream(dest.toString() + "/" + Uri.parse(url).getLastPathSegment()); + byte[] data = new byte[1024]; + int count; + while ((count = bis.read(data, 0, 1024)) != -1) { + fos.write(data, 0, count); + } + } +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/MainActivity.java b/app/src/main/java/com/dphoeniixx/mrfpractice/MainActivity.java new file mode 100644 index 0000000..888959e --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/MainActivity.java @@ -0,0 +1,116 @@ +package com.dphoeniixx.mrfpractice; + +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.os.StrictMode; +import android.view.View; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; +import androidx.navigation.ui.AppBarConfiguration; + +import com.dphoeniixx.mrfpractice.data.SessionManager; +import com.dphoeniixx.mrfpractice.databinding.ActivityMainBinding; +import com.dphoeniixx.mrfpractice.deeplink.DeeplinkHandler; + +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Button; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.util.List; + +public class MainActivity extends FragmentActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + showFragment(new BlogFragment()); + + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(policy); + + Button blogButton = findViewById(R.id.blogBtn); + Button profileButton = findViewById(R.id.profileBtn); + + if(SessionManager.getToken() == "") { + profileButton.setText("Login"); + } else { + profileButton.setText("Profile"); + } + + blogButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + +// Intent in = new Intent(Intent.ACTION_VIEW); +// in.setData(Uri.parse("linkedin://profile/dphoeniixx?src=sdk&accessToken=eeeeeeerrrr")); +// in.setFlags(0); +// startActivityForResult(in, 300); + + showFragment(new BlogFragment()); + } + }); + + profileButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if(SessionManager.getToken() == ""){ + showFragment(new LoginFragment()); + }else { + showFragment(new ProfileFragment()); + } + } + }); + + if (getIntent().getData() != null){ + DeeplinkHandler deeplinkHandler = new DeeplinkHandler(getIntent().getData()); + deeplinkHandler.handle(); + } + + + } + + private void showFragment(Fragment fragment) { + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + fragmentTransaction.replace(R.id.flFragment, fragment, fragment.getClass().getSimpleName()); + fragmentTransaction.commit(); + } + + @Override + public void startActivityForResult(Intent intent, int requestCode) { + try { + super.startActivityForResult(intent, requestCode); + } catch (Exception ignored){} + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/ProfileFragment.java b/app/src/main/java/com/dphoeniixx/mrfpractice/ProfileFragment.java new file mode 100644 index 0000000..61ad403 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/ProfileFragment.java @@ -0,0 +1,130 @@ +package com.dphoeniixx.mrfpractice; + +import static android.content.Context.MODE_PRIVATE; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import com.dphoeniixx.mrfpractice.data.SessionManager; +import com.dphoeniixx.mrfpractice.http.RESTClient; +import com.dphoeniixx.mrfpractice.http.resposnes.ProfileResponse; +import com.dphoeniixx.mrfpractice.http.resposnes.RegisterResponse; +import com.google.gson.Gson; + +import java.io.IOException; +import java.lang.annotation.Annotation; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Response; +import okhttp3.ResponseBody; +import retrofit2.Converter; + +public class ProfileFragment extends Fragment { + + private static final RESTClient API = MRFApp.restClient; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + TextView helloText = getView().findViewById(R.id.helloText); + TextView emailText = getView().findViewById(R.id.emailText); + Button logoutButton = getView().findViewById(R.id.logoutButton); + Button updateButton = getView().findViewById(R.id.updateButton); + + if(SessionManager.getToken() == ""){ + showFragment(new LoginFragment()); + } + + logoutButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + SessionManager.setToken(""); + showFragment(new LoginFragment()); + ((Button)getActivity().findViewById(R.id.profileBtn)).setText("Login"); + } + }); + + updateButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + API.changeEmail(emailText.getText().toString()).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + MRFApp.getCurrentActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MRFApp.getContext(),"Unknown error on updating email.", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + MRFApp.getCurrentActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + if(response.isSuccessful()){ + Toast.makeText(MRFApp.getContext(),"Email Updated.", Toast.LENGTH_SHORT).show(); + }else { + Toast.makeText(MRFApp.getContext(), "Unknown error on updating email.", Toast.LENGTH_SHORT).show(); + } + response.close(); + } + }); + } + }); + } + }); + + API.getProfile().enqueue(new okhttp3.Callback() { + @Override + public void onFailure(okhttp3.Call call, IOException e) { + + } + + @Override + public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException { + if(response.isSuccessful()){ + ProfileResponse profileResponse = new Gson().fromJson(response.body().string(), ProfileResponse.class); + MRFApp.setText(helloText, String.format("Hello, %s\nYour Email: %s", profileResponse.getUser().getName(), profileResponse.getUser().getEmail()) ); + } else { + ProfileResponse.Error error = new Gson().fromJson(response.body().string(), ProfileResponse.Error.class); + MRFApp.setText(helloText, error.getMessage()); + } + response.close(); + } + }); + } + + private void showFragment(Fragment fragment) { + FragmentManager fragmentManager = getParentFragmentManager(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + fragmentTransaction.replace(R.id.flFragment, fragment, fragment.getClass().getSimpleName()); + fragmentTransaction.commit(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_profile, container, false); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/data/BlogAdapter.java b/app/src/main/java/com/dphoeniixx/mrfpractice/data/BlogAdapter.java new file mode 100644 index 0000000..7d90981 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/data/BlogAdapter.java @@ -0,0 +1,72 @@ +package com.dphoeniixx.mrfpractice.data; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.dphoeniixx.mrfpractice.BlogpostActivity; +import com.dphoeniixx.mrfpractice.R; +import com.dphoeniixx.mrfpractice.data.model.Blog; +import com.google.android.material.imageview.ShapeableImageView; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.TimeZone; + +public class BlogAdapter extends ArrayAdapter { + public BlogAdapter(@NonNull Context context, ArrayList dataArrayList) { + super(context, R.layout.blog_post, dataArrayList); + } + @NonNull + @Override + public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) { + Blog Blog = getItem(position); + if (view == null){ + view = LayoutInflater.from(getContext()).inflate(R.layout.blog_post, parent, false); + } + ShapeableImageView blogImage = view.findViewById(R.id.blogImage); + TextView blogTitle = view.findViewById(R.id.blogTitle); + TextView blogTime = view.findViewById(R.id.blogTime); + + blogTitle.setText(Blog.getTitle()); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); + + CharSequence ago = null; + try { + long time = sdf.parse(Blog.getCreatedAt()).getTime(); + long now = System.currentTimeMillis(); + ago = + DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS); + } catch (ParseException e) { + e.printStackTrace(); + } + blogTime.setText(ago); + + ImageDownloader imageDownloader = new ImageDownloader(Uri.parse(Blog.getImage())); + blogImage.setImageBitmap(imageDownloader.download()); + + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intentBlogpost = new Intent(getContext(), BlogpostActivity.class); + intentBlogpost.putExtra(BlogpostActivity.BLOGPOST_ID, Blog.getID()); + getContext().startActivity(intentBlogpost); + } + }); + + + return view; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/data/ImageDownloader.java b/app/src/main/java/com/dphoeniixx/mrfpractice/data/ImageDownloader.java new file mode 100644 index 0000000..3941926 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/data/ImageDownloader.java @@ -0,0 +1,47 @@ +package com.dphoeniixx.mrfpractice.data; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import com.dphoeniixx.mrfpractice.data.Utils; + +import com.dphoeniixx.mrfpractice.MRFApp; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.URL; + +public class ImageDownloader { + + private static Uri downloadUri; + private static File cacheFile; + private static File destFile; + + private static String CACHE_PATH = MRFApp.getContext().getApplicationContext().getCacheDir() + "/image-cache/"; + + public ImageDownloader(Uri uri) { + downloadUri = uri; + destFile = new File(CACHE_PATH, Utils.md5(downloadUri.toString())); + cacheFile = new File(destFile, downloadUri.getLastPathSegment()); + destFile.mkdir(); + } + + public Bitmap download() { + if(isCached()){ + return BitmapFactory.decodeFile(cacheFile.getAbsolutePath()); + } + try { + MRFApp.downloadFile(downloadUri.toString(), destFile); + return BitmapFactory.decodeFile(cacheFile.getAbsolutePath()); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private static boolean isCached(){ + return cacheFile.exists(); + } +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/data/SessionManager.java b/app/src/main/java/com/dphoeniixx/mrfpractice/data/SessionManager.java new file mode 100644 index 0000000..f198745 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/data/SessionManager.java @@ -0,0 +1,20 @@ +package com.dphoeniixx.mrfpractice.data; + +import static android.content.Context.MODE_PRIVATE; + +import android.content.SharedPreferences; + +import com.dphoeniixx.mrfpractice.MRFApp; + +public class SessionManager { + private static SharedPreferences sharedPreferences = MRFApp.getContext().getSharedPreferences("user", MODE_PRIVATE); + private static String TOKEN_NAME = "auth_token"; + + public static String getToken() { + return sharedPreferences.getString(TOKEN_NAME, ""); + } + + public static void setToken(String token){ + sharedPreferences.edit().putString(TOKEN_NAME, token).apply(); + } +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/data/Utils.java b/app/src/main/java/com/dphoeniixx/mrfpractice/data/Utils.java new file mode 100644 index 0000000..88fbdb3 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/data/Utils.java @@ -0,0 +1,31 @@ +package com.dphoeniixx.mrfpractice.data; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +final class Utils { + public static String md5(final String s) { + final String MD5 = "MD5"; + try { + // Create MD5 Hash + MessageDigest digest = java.security.MessageDigest + .getInstance(MD5); + digest.update(s.getBytes()); + byte messageDigest[] = digest.digest(); + + // Create Hex String + StringBuilder hexString = new StringBuilder(); + for (byte aMessageDigest : messageDigest) { + String h = Integer.toHexString(0xFF & aMessageDigest); + while (h.length() < 2) + h = "0" + h; + hexString.append(h); + } + return hexString.toString(); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return ""; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/data/model/Blog.java b/app/src/main/java/com/dphoeniixx/mrfpractice/data/model/Blog.java new file mode 100644 index 0000000..618a08c --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/data/model/Blog.java @@ -0,0 +1,56 @@ +package com.dphoeniixx.mrfpractice.data.model; + +import java.util.ArrayList; +import java.util.Arrays; + +public class Blog { + private String title; + private String description; + private String image; + private String createdAt; + private String _id; + private ArrayList tags; + + public Blog(String _id, String title, String description, String image, ArrayList tags, String createdAt) { + this._id = _id; + this.title = title; + this.description = description; + this.image = image; + this.tags = tags; + this.createdAt = createdAt; + } + + public String getID() { + return _id; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public String getImage() { + return image; + } + + public ArrayList getTags() { + return tags; + } + + public String getCreatedAt() { + return createdAt; + } + + @Override + public String toString() { + return "Blog{" + + "title='" + title + '\'' + + ", description='" + description + '\'' + + ", image='" + image + '\'' + + ", tags=" + tags.toString() + + '}'; + } +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/data/model/User.java b/app/src/main/java/com/dphoeniixx/mrfpractice/data/model/User.java new file mode 100644 index 0000000..64e6df3 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/data/model/User.java @@ -0,0 +1,37 @@ +package com.dphoeniixx.mrfpractice.data.model; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class User { + + @SerializedName("_id") + @Expose + private String id; + @SerializedName("name") + @Expose + private String name; + @SerializedName("email") + @Expose + private String email; + @SerializedName("role") + @Expose + private String role; + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public String getRole() { + return role; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/deeplink/DeeplinkEntry.java b/app/src/main/java/com/dphoeniixx/mrfpractice/deeplink/DeeplinkEntry.java new file mode 100644 index 0000000..ac352bf --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/deeplink/DeeplinkEntry.java @@ -0,0 +1,19 @@ +package com.dphoeniixx.mrfpractice.deeplink; + +public class DeeplinkEntry { + private String entryRegex; + private String handlerMethod; + + public DeeplinkEntry(String regex, String handlerMethod) { + this.entryRegex = regex; + this.handlerMethod = handlerMethod; + } + + public String getRegex() { + return entryRegex; + } + + public String getHandlerMethod() { + return handlerMethod; + } +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/deeplink/DeeplinkHandler.java b/app/src/main/java/com/dphoeniixx/mrfpractice/deeplink/DeeplinkHandler.java new file mode 100644 index 0000000..983d6ba --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/deeplink/DeeplinkHandler.java @@ -0,0 +1,45 @@ +package com.dphoeniixx.mrfpractice.deeplink; + +import android.net.Uri; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DeeplinkHandler { + private static DeeplinkEntry HOME_DEEPLINK = new DeeplinkEntry("^mrf://dphoeniixx/home$", "handleHome"); + private static DeeplinkEntry BLOGPOST_DEEPLINK = new DeeplinkEntry("^mrf://dphoeniixx/blog/(.*?)$", "handleBlog"); + private static DeeplinkEntry REDEEM_DEEPLINK = new DeeplinkEntry("^mrf://dphoeniixx/redeem$", "handleRedeem"); + private static ArrayList deeplinkEntries = new ArrayList(Arrays.asList(HOME_DEEPLINK, BLOGPOST_DEEPLINK, REDEEM_DEEPLINK)); + + private static DeeplinkEntry deeplinkEntry = null; + private static Uri deeplinkUri; + + public DeeplinkHandler(Uri uri) { + deeplinkUri = uri; + deeplinkEntry = getDeeplinkEntry(); + } + + public static DeeplinkEntry getDeeplinkEntry(){ + String URI = String.format("%s://%s%s", deeplinkUri.getScheme(), deeplinkUri.getAuthority(), deeplinkUri.getPath());; + for(DeeplinkEntry entry : deeplinkEntries){ + Pattern pattern = Pattern.compile(entry.getRegex()); + Matcher matcher = pattern.matcher(URI); + if(matcher.matches()){ + return entry; + } + } + return null; + } + + public void handle(){ + if(deeplinkEntry != null){ + try { + DeeplinkHandlers.class.getMethod(deeplinkEntry.getHandlerMethod(), Uri.class).invoke(null, deeplinkUri); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/deeplink/DeeplinkHandlers.java b/app/src/main/java/com/dphoeniixx/mrfpractice/deeplink/DeeplinkHandlers.java new file mode 100644 index 0000000..65289f4 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/deeplink/DeeplinkHandlers.java @@ -0,0 +1,60 @@ +package com.dphoeniixx.mrfpractice.deeplink; + +import android.content.Intent; +import android.net.Uri; +import android.widget.Toast; + +import com.dphoeniixx.mrfpractice.BlogpostActivity; +import com.dphoeniixx.mrfpractice.MRFApp; +import com.dphoeniixx.mrfpractice.MainActivity; +import com.dphoeniixx.mrfpractice.http.RESTClient; + +import java.io.IOException; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Response; + +public class DeeplinkHandlers { + private static final RESTClient API = MRFApp.restClient; + + public static void handleHome(Uri uri) { + Intent intent = new Intent(MRFApp.getContext(), MainActivity.class); + MRFApp.getCurrentActivity().startActivity(intent); + } + + public static void handleBlog(Uri uri) { + Intent intent = new Intent(MRFApp.getContext(), BlogpostActivity.class); + intent.putExtra(BlogpostActivity.BLOGPOST_ID, uri.getPathSegments().get(1)); + MRFApp.getCurrentActivity().startActivity(intent); + } + + public static void handleRedeem(Uri uri) { + String redeemCode = uri.getQueryParameter("code"); + API.redeem(redeemCode).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + MRFApp.getCurrentActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MRFApp.getContext(), "Unknown error on redeeming.", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onResponse(Call call, Response response) { + MRFApp.getCurrentActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + if(response.isSuccessful()){ + Toast.makeText(MRFApp.getContext(),"Redeemed.", Toast.LENGTH_SHORT).show(); + }else { + Toast.makeText(MRFApp.getContext(), "Unknown error on redeeming.", Toast.LENGTH_SHORT).show(); + } + } + }); + } + }); + } +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/http/RESTClient.java b/app/src/main/java/com/dphoeniixx/mrfpractice/http/RESTClient.java new file mode 100644 index 0000000..2d44da8 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/http/RESTClient.java @@ -0,0 +1,232 @@ +package com.dphoeniixx.mrfpractice.http; + +import android.content.Context; +import android.util.Log; + +import com.dphoeniixx.mrfpractice.MRFApp; +import com.dphoeniixx.mrfpractice.R; +import com.dphoeniixx.mrfpractice.data.SessionManager; +import com.dphoeniixx.mrfpractice.http.resposnes.LoginResponse; + +import org.apache.commons.io.IOUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import okhttp3.Authenticator; +import okhttp3.Call; +import okhttp3.CertificatePinner; +import okhttp3.Connection; +import okhttp3.FormBody; +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.Route; +import okio.BufferedSink; + +public class RESTClient { + private static String BASE_URL = "https://52.139.154.230:8443/api/v1"; + + private static String LOGIN_ENDPOINT = "/user/login"; + private static String REGISTER_ENDPOINT = "/user/register"; + private static String UPDATE_ENDPOINT = "/user/update"; + private static String PROFILE_ENDPOINT = "/user/profile"; + private static String GET_BLOG_ENDPOINT = "/blogs/"; + private static String GET_BLOGS_ENDPOINT = "/blogs"; + private static String REDEEM_ENDPOINT = "/user/redeem/"; + + private static OkHttpClient client; + private static Interceptor interceptor = new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + if(SessionManager.getToken() != ""){ + request = request.newBuilder() + .header("Authorization", "Bearer " + SessionManager.getToken()) + .build(); + } + return chain.proceed(request); + } + }; + + + TrustManager TRUST_ALL_CERTS = new X509TrustManager() { + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) { + } + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[] {}; + } + }; + + public static String convertStreamToString(InputStream is) throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line + "\n"); + } + is.close(); + return sb.toString(); + } + + public static SSLContext getSslContext(Context context) throws Exception { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // "BKS" + ks.load(null, null); + + InputStream is = context.getResources().openRawResource(R.raw.certificate); + String certificate = convertStreamToString(is); + + // generate input stream for certificate factory + InputStream stream = IOUtils.toInputStream(certificate); + + // CertificateFactory + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + // certificate + Certificate ca; + try { + ca = cf.generateCertificate(stream); + } finally { + is.close(); + } + + ks.setCertificateEntry("my-ca", ca); + + // TrustManagerFactory + String algorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm); + // Create a TrustManager that trusts the CAs in our KeyStore + tmf.init(ks); + + // Create a SSLContext with the certificate that uses tmf (TrustManager) + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), new SecureRandom()); + + return sslContext; + } + + public RESTClient() { + try { + client = new OkHttpClient.Builder() + .addInterceptor(interceptor) + .addNetworkInterceptor(interceptor) + .sslSocketFactory(getSslContext(MRFApp.getContext()).getSocketFactory()) + .certificatePinner(new CertificatePinner.Builder() + .add("52.139.154.230", "sha256/G34i6LjdY5oBYD33LSpgsg8MIVsOQeilOmlFcngZP7M=") + .build()) + .build(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Call login(String email, String password) { + RequestBody requestBody = new FormBody.Builder() + .add("email", email) + .add("password", password) + .build(); + + Request request = new Request.Builder() + .url(BASE_URL + LOGIN_ENDPOINT) + .post(requestBody) + .build(); + Call call = client.newCall(request); + return call; + } + + public Call register(String name, String email, String password) { + RequestBody requestBody = new FormBody.Builder() + .add("name", name) + .add("email", email) + .add("password", password) + .build(); + + Request request = new Request.Builder() + .url(BASE_URL + REGISTER_ENDPOINT) + .post(requestBody) + .build(); + Call call = client.newCall(request); + return call; + } + + public Call redeem(String code) { + RequestBody requestBody = new RequestBody() { + @Override + public MediaType contentType() { + return null; + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + + } + }; + + Request request = new Request.Builder() + .url(BASE_URL + REDEEM_ENDPOINT + code) + .post(requestBody) + .build(); + Call call = client.newCall(request); + return call; + } + + public Call changeEmail(String email) { + RequestBody requestBody = new FormBody.Builder() + .add("email", email) + .build(); + + Request request = new Request.Builder() + .url(BASE_URL + UPDATE_ENDPOINT) + .post(requestBody) + .build(); + + Call call = client.newCall(request); + return call; + } + + public Call getProfile() { + Request request = new Request.Builder() + .url(BASE_URL + PROFILE_ENDPOINT) + .build(); + Call call = client.newCall(request); + return call; + } + + public Call getBlogById(String blogId) { + Request request = new Request.Builder() + .url(BASE_URL + GET_BLOG_ENDPOINT + blogId) + .build(); + Call call = client.newCall(request); + return call; + } + + public Call getBlogs() { + Request request = new Request.Builder() + .url(BASE_URL + GET_BLOGS_ENDPOINT) + .build(); + Call call = client.newCall(request); + return call; + } +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/BlogResponse.java b/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/BlogResponse.java new file mode 100644 index 0000000..4b857ff --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/BlogResponse.java @@ -0,0 +1,23 @@ +package com.dphoeniixx.mrfpractice.http.resposnes; + +import com.dphoeniixx.mrfpractice.data.model.Blog; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class BlogResponse { + @SerializedName("status") + @Expose + private String status; + @SerializedName("data") + @Expose + private Blog data; + + public String getStatus() { + return status; + } + + public Blog getData() { + return data; + } + +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/BlogsResponse.java b/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/BlogsResponse.java new file mode 100644 index 0000000..8bfd6a0 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/BlogsResponse.java @@ -0,0 +1,25 @@ +package com.dphoeniixx.mrfpractice.http.resposnes; + +import com.dphoeniixx.mrfpractice.data.model.Blog; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +public class BlogsResponse { + @SerializedName("status") + @Expose + private String status; + @SerializedName("data") + @Expose + private ArrayList data; + + public String getStatus() { + return status; + } + + public ArrayList getData() { + return data; + } + +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/LoginResponse.java b/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/LoginResponse.java new file mode 100644 index 0000000..cc794b0 --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/LoginResponse.java @@ -0,0 +1,61 @@ +package com.dphoeniixx.mrfpractice.http.resposnes; + +import com.dphoeniixx.mrfpractice.data.model.User; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class LoginResponse { + + @SerializedName("status") + @Expose + private String status; + + @SerializedName("data") + @Expose + private LoginData data = new LoginData(); + + public String getStatus() { + return status; + } + + public LoginData getData() { + return data; + } + + public class LoginData { + + @SerializedName("token") + @Expose + private String token; + @SerializedName("user") + @Expose + private User user; + + public String getToken() { + return token; + } + + public User getUser() { + return user; + } + + } + + public class Error { + @SerializedName("status") + @Expose + private String status; + @SerializedName("message") + @Expose + private String message; + + public String getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/ProfileResponse.java b/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/ProfileResponse.java new file mode 100644 index 0000000..ce49edc --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/ProfileResponse.java @@ -0,0 +1,41 @@ +package com.dphoeniixx.mrfpractice.http.resposnes; + +import com.dphoeniixx.mrfpractice.data.model.User; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class ProfileResponse { + @SerializedName("status") + @Expose + private String status; + + @SerializedName("data") + @Expose + private User data; + + public String getStatus() { + return status; + } + + public User getUser() { + return data; + } + + public class Error { + @SerializedName("status") + @Expose + private String status; + @SerializedName("message") + @Expose + private String message; + + public String getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + } +} diff --git a/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/RegisterResponse.java b/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/RegisterResponse.java new file mode 100644 index 0000000..73b4b8f --- /dev/null +++ b/app/src/main/java/com/dphoeniixx/mrfpractice/http/resposnes/RegisterResponse.java @@ -0,0 +1,74 @@ +package com.dphoeniixx.mrfpractice.http.resposnes; + +import com.dphoeniixx.mrfpractice.data.model.User; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +public class RegisterResponse { + + @SerializedName("status") + @Expose + private String status; + + @SerializedName("message") + @Expose + private String message; + + public String getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + public class Error { + @SerializedName("status") + @Expose + private String status; + + @SerializedName("errors") + @Expose + private ArrayList errors; + + @SerializedName("message") + @Expose + private String message; + + public String getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + public ArrayList getErrors() { + return errors; + } + } + + public class ValidationError { + @SerializedName("value") + @Expose + private String value; + + @SerializedName("msg") + @Expose + private String msg; + + @SerializedName("param") + @Expose + private String param; + + @SerializedName("location") + @Expose + private String location; + + public String getMsg() { + return msg; + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/font/advent_pro_thin.ttf b/app/src/main/res/font/advent_pro_thin.ttf new file mode 100644 index 0000000..5f21778 Binary files /dev/null and b/app/src/main/res/font/advent_pro_thin.ttf differ diff --git a/app/src/main/res/layout/activity_blog_post.xml b/app/src/main/res/layout/activity_blog_post.xml new file mode 100644 index 0000000..9d14c9a --- /dev/null +++ b/app/src/main/res/layout/activity_blog_post.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..38e011a --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,38 @@ + + + +