Add setting to disable screenshots. https://redmine.stoutner.com/issues/266
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / PinnedSslCertificateMismatchDialog.java
1 /*
2  * Copyright © 2017-2018 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs;
21
22 import android.annotation.SuppressLint;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.net.http.SslCertificate;
28 import android.os.Bundle;
29 import android.support.annotation.NonNull;
30 import android.support.design.widget.TabLayout;
31 import android.support.v4.view.PagerAdapter;
32 // `AppCompatDialogFragment` is used instead of `DialogFragment` to avoid an error on API <=22.
33 import android.support.v7.app.AppCompatDialogFragment;
34 import android.text.SpannableStringBuilder;
35 import android.text.Spanned;
36 import android.text.style.ForegroundColorSpan;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.view.WindowManager;
41 import android.widget.TextView;
42
43 import com.stoutner.privacybrowser.R;
44 import com.stoutner.privacybrowser.activities.MainWebViewActivity;
45 import com.stoutner.privacybrowser.definitions.WrapVerticalContentViewPager;
46 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
47
48 import java.text.DateFormat;
49 import java.util.Date;
50
51 public class PinnedSslCertificateMismatchDialog extends AppCompatDialogFragment {
52     // `layoutInflater` is used in `onCreateDialog()` and `pagerAdapter`.
53     private LayoutInflater layoutInflater;
54
55     // The current website SSL certificate variables are used in `onCreateDialog()` and `pagerAdapter()`.
56     private String currentSslIssuedToCNameString;
57     private String currentSslIssuedToONameString;
58     private String currentSslIssuedToUNameString;
59     private String currentSslIssuedByCNameString;
60     private String currentSslIssuedByONameString;
61     private String currentSslIssuedByUNameString;
62     private Date currentSslStartDate;
63     private Date currentSslEndDate;
64
65     // The public interface is used to send information back to the parent activity.
66     public interface PinnedSslCertificateMismatchListener {
67         void onSslMismatchBack();
68
69         void onSslMismatchProceed();
70     }
71
72     // `sslCertificateErrorListener` is used in `onAttach` and `onCreateDialog`.
73     private PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener pinnedSslCertificateMismatchListener;
74
75     // Check to make sure that the parent activity implements the listener.
76     public void onAttach(Context context) {
77         super.onAttach(context);
78
79         try {
80             pinnedSslCertificateMismatchListener = (PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener) context;
81         } catch(ClassCastException exception) {
82             throw new ClassCastException(context.toString() + " must implement PinnedSslCertificateMismatchListener");
83         }
84     }
85
86     // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`.
87     @SuppressLint("InflateParams")
88     @Override
89     @NonNull
90     public Dialog onCreateDialog(Bundle savedInstanceState) {
91         // Remove the incorrect lint warning that `getActivity()` might be null.
92         assert getActivity() != null;
93
94         // Get the activity's layout inflater.
95         layoutInflater = getActivity().getLayoutInflater();
96
97         // Use an alert dialog builder to create the alert dialog.
98         AlertDialog.Builder dialogBuilder;
99
100         // Set the style according to the theme.
101         if (MainWebViewActivity.darkTheme) {
102             // Set the dialog theme.
103             dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark);
104
105             // Set the icon.
106             dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_dark);
107         } else {
108             // Set the dialog theme.
109             dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight);
110
111             // Set the icon.
112             dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_light);
113         }
114
115         // Setup the neutral button.
116         dialogBuilder.setNeutralButton(R.string.update_ssl, (DialogInterface dialog, int which) -> {
117             // Initialize the `long` date variables.  If the date is `null`, a long value of `0` will be stored in the Domains database entry.
118             long currentSslStartDateLong = 0;
119             long currentSslEndDateLong = 0;
120
121             // Convert the `Dates` into `longs`.
122             if (currentSslStartDate != null) {
123                 currentSslStartDateLong = currentSslStartDate.getTime();
124             }
125
126             if (currentSslEndDate != null) {
127                 currentSslEndDateLong = currentSslEndDate.getTime();
128             }
129
130             // Initialize the database handler.  The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
131             DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(getContext(), null, null, 0);
132
133             // Update the pinned SSL certificate for this domain.
134             domainsDatabaseHelper.updateCertificate(MainWebViewActivity.domainSettingsDatabaseId, currentSslIssuedToCNameString, currentSslIssuedToONameString, currentSslIssuedToUNameString,
135                     currentSslIssuedByCNameString, currentSslIssuedByONameString, currentSslIssuedByUNameString, currentSslStartDateLong, currentSslEndDateLong);
136
137             // Update the pinned SSL certificate global variables to match the information that is now in the database.
138             MainWebViewActivity.pinnedDomainSslIssuedToCNameString = currentSslIssuedToCNameString;
139             MainWebViewActivity.pinnedDomainSslIssuedToONameString = currentSslIssuedToONameString;
140             MainWebViewActivity.pinnedDomainSslIssuedToUNameString = currentSslIssuedToUNameString;
141             MainWebViewActivity.pinnedDomainSslIssuedByCNameString = currentSslIssuedByCNameString;
142             MainWebViewActivity.pinnedDomainSslIssuedByONameString = currentSslIssuedByONameString;
143             MainWebViewActivity.pinnedDomainSslIssuedByUNameString = currentSslIssuedByUNameString;
144             MainWebViewActivity.pinnedDomainSslStartDate = currentSslStartDate;
145             MainWebViewActivity.pinnedDomainSslEndDate = currentSslEndDate;
146         });
147
148         // Setup the negative button.
149         dialogBuilder.setNegativeButton(R.string.back, (DialogInterface dialog, int which) -> {
150             // Call the `onSslMismatchBack` public interface to send the `WebView` back one page.
151             pinnedSslCertificateMismatchListener.onSslMismatchBack();
152         });
153
154         // Setup the positive button.
155         dialogBuilder.setPositiveButton(R.string.proceed, (DialogInterface dialog, int which) -> {
156             // Call the `onSslMismatchProceed` public interface.
157             pinnedSslCertificateMismatchListener.onSslMismatchProceed();
158         });
159
160         // Set the title.
161         dialogBuilder.setTitle(R.string.ssl_certificate_mismatch);
162
163         // Set the layout.  The parent view is `null` because it will be assigned by `AlertDialog`.
164         dialogBuilder.setView(layoutInflater.inflate(R.layout.pinned_ssl_certificate_mismatch_linearlayout, null));
165
166         // Create an alert dialog from the alert dialog builder.
167         final AlertDialog alertDialog = dialogBuilder.create();
168
169         // Disable screenshots if not allowed.
170         if (!MainWebViewActivity.allowScreenshots) {
171             // Remove the warning below that `getWindow()` might be null.
172             assert alertDialog.getWindow() != null;
173
174             // Disable screenshots.
175             alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
176         }
177
178         // Show the alert dialog so the items in the layout can be modified.
179         alertDialog.show();
180
181         //  Setup the view pager.
182         WrapVerticalContentViewPager wrapVerticalContentViewPager = alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_viewpager);
183         wrapVerticalContentViewPager.setAdapter(new pagerAdapter());
184
185         // Setup the tab layout and connect it to the view pager.
186         TabLayout tabLayout = alertDialog.findViewById(R.id.pinned_ssl_certificate_mismatch_tablayout);
187         tabLayout.setupWithViewPager(wrapVerticalContentViewPager);
188
189         // `onCreateDialog()` requires the return of an `AlertDialog`.
190         return alertDialog;
191     }
192
193     private class pagerAdapter extends PagerAdapter {
194         @Override
195         public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
196             // Check to see if the `View` and the `Object` are the same.
197             return (view == object);
198         }
199
200         @Override
201         public int getCount() {
202             // There are two tabs.
203             return 2;
204         }
205
206         @Override
207         public CharSequence getPageTitle(int position) {
208             // Return the current tab title.
209             if (position == 0) {  // The current SSL certificate tab.
210                 return getString(R.string.current_ssl);
211             } else {  // The pinned SSL certificate tab.
212                 return getString(R.string.pinned_ssl);
213             }
214         }
215
216         @Override
217         @NonNull
218         public Object instantiateItem(@NonNull ViewGroup container, int position) {
219             // Inflate the `ScrollView` for this tab.
220             ViewGroup tabViewGroup = (ViewGroup) layoutInflater.inflate(R.layout.pinned_ssl_certificate_mismatch_scrollview, container, false);
221
222             // Get handles for the `TextViews`.
223             TextView issuedToCNameTextView = tabViewGroup.findViewById(R.id.issued_to_cname);
224             TextView issuedToONameTextView = tabViewGroup.findViewById(R.id.issued_to_oname);
225             TextView issuedToUNameTextView = tabViewGroup.findViewById(R.id.issued_to_uname);
226             TextView issuedByCNameTextView = tabViewGroup.findViewById(R.id.issued_by_cname);
227             TextView issuedByONameTextView = tabViewGroup.findViewById(R.id.issued_by_oname);
228             TextView issuedByUNameTextView = tabViewGroup.findViewById(R.id.issued_by_uname);
229             TextView startDateTextView = tabViewGroup.findViewById(R.id.start_date);
230             TextView endDateTextView = tabViewGroup.findViewById(R.id.end_date);
231
232             // Setup the labels.
233             String cNameLabel = getString(R.string.common_name) + "  ";
234             String oNameLabel = getString(R.string.organization) + "  ";
235             String uNameLabel = getString(R.string.organizational_unit) + "  ";
236             String startDateLabel = getString(R.string.start_date) + "  ";
237             String endDateLabel = getString(R.string.end_date) + "  ";
238
239             // Get the current website SSL certificate.
240             SslCertificate sslCertificate = MainWebViewActivity.sslCertificate;
241
242             // Extract the individual pieces of information from the current website SSL certificate if it is not null.
243             if (sslCertificate != null) {
244                 currentSslIssuedToCNameString = sslCertificate.getIssuedTo().getCName();
245                 currentSslIssuedToONameString = sslCertificate.getIssuedTo().getOName();
246                 currentSslIssuedToUNameString = sslCertificate.getIssuedTo().getUName();
247                 currentSslIssuedByCNameString = sslCertificate.getIssuedBy().getCName();
248                 currentSslIssuedByONameString = sslCertificate.getIssuedBy().getOName();
249                 currentSslIssuedByUNameString = sslCertificate.getIssuedBy().getUName();
250                 currentSslStartDate = sslCertificate.getValidNotBeforeDate();
251                 currentSslEndDate = sslCertificate.getValidNotAfterDate();
252             } else {
253                 // Initialize the current website SSL certificate variables with blank information.
254                 currentSslIssuedToCNameString = "";
255                 currentSslIssuedToONameString = "";
256                 currentSslIssuedToUNameString = "";
257                 currentSslIssuedByCNameString = "";
258                 currentSslIssuedByONameString = "";
259                 currentSslIssuedByUNameString = "";
260             }
261
262             // Initialize the `SpannableStringBuilders`.
263             SpannableStringBuilder issuedToCNameStringBuilder;
264             SpannableStringBuilder issuedToONameStringBuilder;
265             SpannableStringBuilder issuedToUNameStringBuilder;
266             SpannableStringBuilder issuedByCNameStringBuilder;
267             SpannableStringBuilder issuedByONameStringBuilder;
268             SpannableStringBuilder issuedByUNameStringBuilder;
269             SpannableStringBuilder startDateStringBuilder;
270             SpannableStringBuilder endDateStringBuilder;
271
272             // Setup the `SpannableStringBuilders` for each tab.
273             if (position == 0) {  // Setup the current SSL certificate tab.
274                 issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedToCNameString);
275                 issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedToONameString);
276                 issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedToUNameString);
277                 issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + currentSslIssuedByCNameString);
278                 issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + currentSslIssuedByONameString);
279                 issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + currentSslIssuedByUNameString);
280
281                 // Set the dates if they aren't `null`.
282                 if (currentSslStartDate == null) {
283                     startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
284                 } else {
285                     startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslStartDate));
286                 }
287
288                 if (currentSslEndDate == null) {
289                     endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
290                 } else {
291                     endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(currentSslEndDate));
292                 }
293             } else {  // Setup the pinned SSL certificate tab.
294                 issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToCNameString);
295                 issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToONameString);
296                 issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + MainWebViewActivity.pinnedDomainSslIssuedToUNameString);
297                 issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByCNameString);
298                 issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByONameString);
299                 issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + MainWebViewActivity.pinnedDomainSslIssuedByUNameString);
300
301                 // Set the dates if they aren't `null`.
302                 if (MainWebViewActivity.pinnedDomainSslStartDate == null) {
303                     startDateStringBuilder = new SpannableStringBuilder(startDateLabel);
304                 } else {
305                     startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG)
306                             .format(MainWebViewActivity.pinnedDomainSslStartDate));
307                 }
308
309                 if (MainWebViewActivity.pinnedDomainSslEndDate == null) {
310                     endDateStringBuilder = new SpannableStringBuilder(endDateLabel);
311                 } else {
312                     endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(MainWebViewActivity.pinnedDomainSslEndDate));
313                 }
314             }
315
316             // Create a red `ForegroundColorSpan`.  We have to use the deprecated `getColor` until API >= 23.
317             @SuppressWarnings("deprecation") ForegroundColorSpan redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
318
319             // Create a blue `ForegroundColorSpan`.
320             ForegroundColorSpan blueColorSpan;
321
322             // Set `blueColorSpan` according to the theme.  We have to use the deprecated `getColor()` until API >= 23.
323             if (MainWebViewActivity.darkTheme) {
324                 //noinspection deprecation
325                 blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400));
326             } else {
327                 //noinspection deprecation
328                 blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700));
329             }
330
331             // Configure the spans to display conflicting information in red.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
332             if (currentSslIssuedToCNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToCNameString)) {
333                 issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
334             } else {
335                 issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
336             }
337
338             if (currentSslIssuedToONameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToONameString)) {
339                 issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
340             } else {
341                 issuedToONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
342             }
343
344             if (currentSslIssuedToUNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedToUNameString)) {
345                 issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
346             } else {
347                 issuedToUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
348             }
349
350             if (currentSslIssuedByCNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByCNameString)) {
351                 issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
352             } else {
353                 issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
354             }
355
356             if (currentSslIssuedByONameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByONameString)) {
357                 issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
358             } else {
359                 issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
360             }
361
362             if (currentSslIssuedByUNameString.equals(MainWebViewActivity.pinnedDomainSslIssuedByUNameString)) {
363                 issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
364             } else {
365                 issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
366             }
367
368             if ((currentSslStartDate != null) && (MainWebViewActivity.pinnedDomainSslStartDate != null) && currentSslStartDate.equals(MainWebViewActivity.pinnedDomainSslStartDate)) {
369                 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
370             } else {
371                 startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
372             }
373
374             if ((currentSslEndDate != null) && (MainWebViewActivity.pinnedDomainSslEndDate != null) && currentSslEndDate.equals(MainWebViewActivity.pinnedDomainSslEndDate)) {
375                 endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
376             } else {
377                 endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
378             }
379
380             // Display the strings.
381             issuedToCNameTextView.setText(issuedToCNameStringBuilder);
382             issuedToONameTextView.setText(issuedToONameStringBuilder);
383             issuedToUNameTextView.setText(issuedToUNameStringBuilder);
384             issuedByCNameTextView.setText(issuedByCNameStringBuilder);
385             issuedByONameTextView.setText(issuedByONameStringBuilder);
386             issuedByUNameTextView.setText(issuedByUNameStringBuilder);
387             startDateTextView.setText(startDateStringBuilder);
388             endDateTextView.setText(endDateStringBuilder);
389
390             // Display the tab.
391             container.addView(tabViewGroup);
392
393             // Make it so.
394             return tabViewGroup;
395         }
396     }
397 }