From ff6b6b5b491d034ad13e3cfa6a4efc2fb9041275 Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:20 +0100 Subject: [PATCH 01/45] Add ._.DS_Store to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fc41250c4..067431411 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ fuzzing-corpora/ *.tar.bz2 *.tar.gz .DS_Store +._.DS_Store coverage/ *.gcno *.gcda From 5d192246e89858c433152b4009e17fa9ae13fc3a Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:21 +0100 Subject: [PATCH 02/45] android: Remove AndroidX legacy support --- src/frontends/android/app/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle index 7aa4d8454..195422791 100644 --- a/src/frontends/android/app/build.gradle +++ b/src/frontends/android/app/build.gradle @@ -46,7 +46,6 @@ android { dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.preference:preference:1.2.1' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'com.google.android.material:material:1.10.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.8.0' From a3e895b4d8b9128e509ec2d41bf9b0504ea9b410 Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:21 +0100 Subject: [PATCH 03/45] android: Remove unnecessary API checks The minSdkVersion is 21, remove unnecessary checks and code that target older API versions. --- .../android/logic/CharonVpnService.java | 37 +++-- .../android/logic/StrongSwanApplication.java | 37 ++--- .../strongswan/android/ui/MainActivity.java | 43 +++--- .../ui/TrustedCertificateImportActivity.java | 3 +- .../ui/TrustedCertificatesActivity.java | 17 +-- .../android/ui/VpnProfileDetailActivity.java | 130 +++++++++--------- .../android/ui/VpnProfileImportActivity.java | 50 +++---- 7 files changed, 142 insertions(+), 175 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java index 165f479dc..03102502d 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java @@ -101,11 +101,12 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe private volatile boolean mTerminate; private volatile boolean mIsDisconnecting; private volatile boolean mShowNotification; - private BuilderAdapter mBuilderAdapter = new BuilderAdapter(); + private final BuilderAdapter mBuilderAdapter = new BuilderAdapter(); private Handler mHandler; private VpnStateService mService; private final Object mServiceLock = new Object(); - private final ServiceConnection mServiceConnection = new ServiceConnection() { + private final ServiceConnection mServiceConnection = new ServiceConnection() + { @Override public void onServiceDisconnected(ComponentName name) { /* since the service is local this is theoretically only called when the process is terminated */ @@ -346,7 +347,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe { synchronized (this) { - if (mNextProfile != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + if (mNextProfile != null) { mBuilderAdapter.setProfile(mNextProfile); mBuilderAdapter.establishBlocking(); @@ -437,10 +438,10 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe name = profile.getName(); } NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL) - .setSmallIcon(R.drawable.ic_notification) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setVisibility(publicVersion ? NotificationCompat.VISIBILITY_PUBLIC - : NotificationCompat.VISIBILITY_PRIVATE); + .setSmallIcon(R.drawable.ic_notification) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setVisibility(publicVersion ? NotificationCompat.VISIBILITY_PUBLIC + : NotificationCompat.VISIBILITY_PRIVATE); int s = R.string.state_disabled; if (error != ErrorState.NO_ERROR) { @@ -527,10 +528,11 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe } @Override - public void stateChanged() { + public void stateChanged() + { if (mShowNotification) { - NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(VPN_STATE_NOTIFICATION_ID, buildNotification(false)); } } @@ -813,7 +815,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe private VpnService.Builder mBuilder; private BuilderCache mCache; private BuilderCache mEstablishedCache; - private PacketDropper mDropper = new PacketDropper(); + private final PacketDropper mDropper = new PacketDropper(); public synchronized void setProfile(VpnProfile profile) { @@ -1071,7 +1073,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe } } } - catch (ClosedByInterruptException|InterruptedException e) + catch (ClosedByInterruptException | InterruptedException e) { /* regular interruption */ } @@ -1277,7 +1279,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe } } } - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + else { /* allow traffic that would otherwise be blocked to bypass the VPN */ builder.allowFamily(OsConstants.AF_INET); } @@ -1317,7 +1319,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe } } } - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + else { builder.allowFamily(OsConstants.AF_INET6); } @@ -1327,8 +1329,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe builder.addRoute("::", 0); } /* apply selected applications */ - if (mSelectedApps.size() > 0 && - Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + if (mSelectedApps.size() > 0) { switch (mAppHandling) { @@ -1372,11 +1373,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe { return false; } - else if (addr instanceof Inet6Address) - { - return true; - } - return false; + return addr instanceof Inet6Address; } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java index 92a112a6d..ac9866155 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java @@ -16,20 +16,18 @@ package org.strongswan.android.logic; +import android.app.Application; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; + +import org.strongswan.android.security.LocalCertificateKeyStoreProvider; + import java.security.Security; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.strongswan.android.security.LocalCertificateKeyStoreProvider; -import org.strongswan.android.ui.MainActivity; - -import android.app.Application; -import android.content.Context; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; - import androidx.core.os.HandlerCompat; public class StrongSwanApplication extends Application @@ -38,7 +36,8 @@ public class StrongSwanApplication extends Application private final ExecutorService mExecutorService = Executors.newFixedThreadPool(4); private final Handler mMainHandler = HandlerCompat.createAsync(Looper.getMainLooper()); - static { + static + { Security.addProvider(new LocalCertificateKeyStoreProvider()); } @@ -51,6 +50,7 @@ public class StrongSwanApplication extends Application /** * Returns the current application context + * * @return context */ public static Context getContext() @@ -60,6 +60,7 @@ public class StrongSwanApplication extends Application /** * Returns a thread pool to run tasks in separate threads + * * @return thread pool */ public Executor getExecutor() @@ -69,6 +70,7 @@ public class StrongSwanApplication extends Application /** * Returns a handler to execute stuff by the main thread. + * * @return handler */ public Handler getHandler() @@ -82,21 +84,6 @@ public class StrongSwanApplication extends Application */ static { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) - { - System.loadLibrary("strongswan"); - - if (MainActivity.USE_BYOD) - { - System.loadLibrary("tpmtss"); - System.loadLibrary("tncif"); - System.loadLibrary("tnccs"); - System.loadLibrary("imcv"); - } - - System.loadLibrary("charon"); - System.loadLibrary("ipsec"); - } System.loadLibrary("androidbridge"); } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java index f2d8939ec..a48a0a886 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java @@ -21,7 +21,6 @@ package org.strongswan.android.ui; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.text.format.Formatter; import android.view.Menu; @@ -82,16 +81,6 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec return true; } - @Override - public boolean onPrepareOptionsMenu(Menu menu) - { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) - { - menu.removeItem(R.id.menu_import_profile); - } - return true; - } - @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -195,26 +184,26 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec size = Formatter.formatFileSize(getActivity(), s); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.clear_crl_cache_title) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() + .setTitle(R.string.clear_crl_cache_title) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) { - @Override - public void onClick(DialogInterface dialog, int which) - { - dismiss(); - } - }) - .setPositiveButton(R.string.clear, new DialogInterface.OnClickListener() + dismiss(); + } + }) + .setPositiveButton(R.string.clear, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int whichButton) { - @Override - public void onClick(DialogInterface dialog, int whichButton) + for (String file : list) { - for (String file : list) - { - getActivity().deleteFile(file); - } + getActivity().deleteFile(file); } - }); + } + }); builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.clear_crl_cache_msg, list.size(), list.size(), size)); return builder.create(); } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java index def0b88a5..4e1e39e2d 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java @@ -78,7 +78,7 @@ public class TrustedCertificateImportActivity extends AppCompatActivity { importCertificate(intent.getData()); } - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + else { Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); openIntent.setType("*/*"); @@ -89,7 +89,6 @@ public class TrustedCertificateImportActivity extends AppCompatActivity catch (ActivityNotFoundException e) { /* some devices are unable to browse for files */ finish(); - return; } } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java index c941a49e9..c32ec5d82 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java @@ -17,7 +17,6 @@ package org.strongswan.android.ui; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; @@ -73,10 +72,10 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr mAdapter = new TrustedCertificatesPagerAdapter(this); - mPager = (ViewPager2)findViewById(R.id.viewpager); + mPager = findViewById(R.id.viewpager); mPager.setAdapter(mAdapter); - TabLayout tabs = (TabLayout)findViewById(R.id.tabs); + TabLayout tabs = findViewById(R.id.tabs); new TabLayoutMediator(tabs, mPager, (tab, position) -> { tab.setText(mAdapter.getTitle(position)); }).attach(); @@ -91,16 +90,6 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr return true; } - @Override - public boolean onPrepareOptionsMenu(Menu menu) - { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) - { - menu.removeItem(R.id.menu_import_certificate); - } - return true; - } - @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -164,7 +153,7 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr public static class TrustedCertificatesPagerAdapter extends FragmentStateAdapter { - private TrustedCertificatesTab mTabs[]; + private final TrustedCertificatesTab[] mTabs; public TrustedCertificatesPagerAdapter(@NonNull FragmentActivity fragmentActivity) { diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index dc3bc1cc7..85d178e52 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -22,7 +22,6 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.security.KeyChain; @@ -44,7 +43,6 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; -import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; @@ -195,64 +193,64 @@ public class VpnProfileDetailActivity extends AppCompatActivity setContentView(R.layout.profile_detail_view); - mName = (MultiAutoCompleteTextView)findViewById(R.id.name); - mNameWrap = (TextInputLayoutHelper)findViewById(R.id.name_wrap); - mGateway = (EditText)findViewById(R.id.gateway); - mGatewayWrap = (TextInputLayoutHelper) findViewById(R.id.gateway_wrap); - mSelectVpnType = (Spinner)findViewById(R.id.vpn_type); - mTncNotice = (RelativeLayout)findViewById(R.id.tnc_notice); + mName = findViewById(R.id.name); + mNameWrap = findViewById(R.id.name_wrap); + mGateway = findViewById(R.id.gateway); + mGatewayWrap = findViewById(R.id.gateway_wrap); + mSelectVpnType = findViewById(R.id.vpn_type); + mTncNotice = findViewById(R.id.tnc_notice); - mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group); - mUsername = (EditText)findViewById(R.id.username); - mUsernameWrap = (TextInputLayoutHelper) findViewById(R.id.username_wrap); - mPassword = (EditText)findViewById(R.id.password); + mUsernamePassword = findViewById(R.id.username_password_group); + mUsername = findViewById(R.id.username); + mUsernameWrap = findViewById(R.id.username_wrap); + mPassword = findViewById(R.id.password); - mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group); - mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate); + mUserCertificate = findViewById(R.id.user_certificate_group); + mSelectUserCert = findViewById(R.id.select_user_certificate); - mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); - mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate); + mCheckAuto = findViewById(R.id.ca_auto); + mSelectCert = findViewById(R.id.select_certificate); - mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced); - mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings); + mShowAdvanced = findViewById(R.id.show_advanced); + mAdvancedSettings = findViewById(R.id.advanced_settings); - mRemoteId = (MultiAutoCompleteTextView)findViewById(R.id.remote_id); - mRemoteIdWrap = (TextInputLayoutHelper) findViewById(R.id.remote_id_wrap); + mRemoteId = findViewById(R.id.remote_id); + mRemoteIdWrap = findViewById(R.id.remote_id_wrap); mLocalId = findViewById(R.id.local_id); mLocalIdWrap = findViewById(R.id.local_id_wrap); mDnsServers = findViewById(R.id.dns_servers); mDnsServersWrap = findViewById(R.id.dns_servers_wrap); - mMTU = (EditText)findViewById(R.id.mtu); - mMTUWrap = (TextInputLayoutHelper) findViewById(R.id.mtu_wrap); - mPort = (EditText)findViewById(R.id.port); - mPortWrap = (TextInputLayoutHelper) findViewById(R.id.port_wrap); - mNATKeepalive = (EditText)findViewById(R.id.nat_keepalive); - mNATKeepaliveWrap = (TextInputLayoutHelper) findViewById(R.id.nat_keepalive_wrap); + mMTU = findViewById(R.id.mtu); + mMTUWrap = findViewById(R.id.mtu_wrap); + mPort = findViewById(R.id.port); + mPortWrap = findViewById(R.id.port_wrap); + mNATKeepalive = findViewById(R.id.nat_keepalive); + mNATKeepaliveWrap = findViewById(R.id.nat_keepalive_wrap); mCertReq = findViewById(R.id.cert_req); mUseCrl = findViewById(R.id.use_crl); mUseOcsp = findViewById(R.id.use_ocsp); - mStrictRevocation= findViewById(R.id.strict_revocation); - mRsaPss= findViewById(R.id.rsa_pss); - mIPv6Transport= findViewById(R.id.ipv6_transport); - mIncludedSubnets = (EditText)findViewById(R.id.included_subnets); - mIncludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.included_subnets_wrap); - mExcludedSubnets = (EditText)findViewById(R.id.excluded_subnets); - mExcludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.excluded_subnets_wrap); - mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4); - mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6); + mStrictRevocation = findViewById(R.id.strict_revocation); + mRsaPss = findViewById(R.id.rsa_pss); + mIPv6Transport = findViewById(R.id.ipv6_transport); + mIncludedSubnets = findViewById(R.id.included_subnets); + mIncludedSubnetsWrap = findViewById(R.id.included_subnets_wrap); + mExcludedSubnets = findViewById(R.id.excluded_subnets); + mExcludedSubnetsWrap = findViewById(R.id.excluded_subnets_wrap); + mBlockIPv4 = findViewById(R.id.split_tunneling_v4); + mBlockIPv6 = findViewById(R.id.split_tunneling_v6); - mSelectSelectedAppsHandling = (Spinner)findViewById(R.id.apps_handling); - mSelectApps = (RelativeLayout)findViewById(R.id.select_applications); + mSelectSelectedAppsHandling = findViewById(R.id.apps_handling); + mSelectApps = findViewById(R.id.select_applications); - mIkeProposal = (EditText)findViewById(R.id.ike_proposal); - mIkeProposalWrap = (TextInputLayoutHelper)findViewById(R.id.ike_proposal_wrap); - mEspProposal = (EditText)findViewById(R.id.esp_proposal); - mEspProposalWrap = (TextInputLayoutHelper)findViewById(R.id.esp_proposal_wrap); + mIkeProposal = findViewById(R.id.ike_proposal); + mIkeProposalWrap = findViewById(R.id.ike_proposal_wrap); + mEspProposal = findViewById(R.id.esp_proposal); + mEspProposalWrap = findViewById(R.id.esp_proposal_wrap); /* make the link clickable */ ((TextView)findViewById(R.id.proposal_intro)).setMovementMethod(LinkMovementMethod.getInstance()); - mProfileIdLabel = (TextView)findViewById(R.id.profile_id_label); - mProfileId = (TextView)findViewById(R.id.profile_id); + mProfileIdLabel = findViewById(R.id.profile_id_label); + mProfileId = findViewById(R.id.profile_id); final SpaceTokenizer spaceTokenizer = new SpaceTokenizer(); mName.setTokenizer(spaceTokenizer); @@ -262,14 +260,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity mName.setAdapter(gatewayAdapter); mRemoteId.setAdapter(gatewayAdapter); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) + mGateway.addTextChangedListener(new TextWatcher() { - findViewById(R.id.apps).setVisibility(View.GONE); - mSelectSelectedAppsHandling.setVisibility(View.GONE); - mSelectApps.setVisibility(View.GONE); - } - - mGateway.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @@ -294,7 +286,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity } }); - mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() { + mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() + { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { @@ -312,7 +305,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity ((TextView)mTncNotice.findViewById(android.R.id.text1)).setText(R.string.tnc_notice_title); ((TextView)mTncNotice.findViewById(android.R.id.text2)).setText(R.string.tnc_notice_subtitle); - mTncNotice.setOnClickListener(new OnClickListener() { + mTncNotice.setOnClickListener(new OnClickListener() + { @Override public void onClick(View v) { @@ -321,14 +315,15 @@ public class VpnProfileDetailActivity extends AppCompatActivity }); mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener()); - ((Button)findViewById(R.id.install_user_certificate)).setOnClickListener(v -> { + findViewById(R.id.install_user_certificate).setOnClickListener(v -> { Intent intent = KeyChain.createInstallIntent(); mInstallPKCS12.launch(intent); }); mSelectUserIdAdapter = new CertificateIdentitiesAdapter(this); mLocalId.setAdapter(mSelectUserIdAdapter); - mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() { + mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() + { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -336,7 +331,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity } }); - mSelectCert.setOnClickListener(new OnClickListener() { + mSelectCert.setOnClickListener(new OnClickListener() + { @Override public void onClick(View v) { @@ -346,7 +342,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity } }); - mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() { + mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() + { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -354,7 +351,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity } }); - mSelectSelectedAppsHandling.setOnItemSelectedListener(new OnItemSelectedListener() { + mSelectSelectedAppsHandling.setOnItemSelectedListener(new OnItemSelectedListener() + { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { @@ -370,7 +368,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity } }); - mSelectApps.setOnClickListener(new OnClickListener() { + mSelectApps.setOnClickListener(new OnClickListener() + { @Override public void onClick(View v) { @@ -489,7 +488,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity AlertDialog.Builder adb = new AlertDialog.Builder(VpnProfileDetailActivity.this); adb.setTitle(R.string.alert_text_nocertfound_title); adb.setMessage(R.string.alert_text_nocertfound); - adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() + { @Override public void onClick(DialogInterface dialog, int id) { @@ -624,6 +624,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity /** * Verify the user input and display error messages. + * * @return true if the input is valid */ private boolean verifyInput() @@ -969,7 +970,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity { final X509Certificate[] chain = KeyChain.getCertificateChain(VpnProfileDetailActivity.this, alias); /* alias() is not called from our main thread */ - runOnUiThread(new Runnable() { + runOnUiThread(new Runnable() + { @Override public void run() { @@ -992,7 +994,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity /** * Callback interface for the user certificate loader. */ - private interface UserCertificateLoaderCallback { + private interface UserCertificateLoaderCallback + { void onComplete(X509Certificate result); } @@ -1050,7 +1053,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity return new AlertDialog.Builder(getActivity()) .setTitle(R.string.tnc_notice_title) .setMessage(HtmlCompat.fromHtml(getString(R.string.tnc_notice_details), HtmlCompat.FROM_HTML_MODE_LEGACY)) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() + { @Override public void onClick(DialogInterface dialog, int id) { @@ -1111,7 +1115,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity if (text instanceof Spanned) { SpannableString sp = new SpannableString(text + " "); - TextUtils.copySpansFrom((Spanned) text, 0, text.length(), Object.class, sp, 0); + TextUtils.copySpansFrom((Spanned)text, 0, text.length(), Object.class, sp, 0); return sp; } else diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java index d7a069d0b..c58365d9c 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java @@ -21,7 +21,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.security.KeyChain; import android.security.KeyChainAliasCallback; @@ -135,12 +134,12 @@ public class VpnProfileImportActivity extends AppCompatActivity } ); - private LoaderManager.LoaderCallbacks mProfileLoaderCallbacks = new LoaderManager.LoaderCallbacks() + private final LoaderManager.LoaderCallbacks mProfileLoaderCallbacks = new LoaderManager.LoaderCallbacks() { @Override public Loader onCreateLoader(int id, Bundle args) { - return new ProfileLoader(VpnProfileImportActivity.this, (Uri)args.getParcelable(PROFILE_URI)); + return new ProfileLoader(VpnProfileImportActivity.this, args.getParcelable(PROFILE_URI)); } @Override @@ -156,7 +155,7 @@ public class VpnProfileImportActivity extends AppCompatActivity } }; - private LoaderManager.LoaderCallbacks mUserCertificateLoaderCallbacks = new LoaderManager.LoaderCallbacks() + private final LoaderManager.LoaderCallbacks mUserCertificateLoaderCallbacks = new LoaderManager.LoaderCallbacks() { @Override public Loader onCreateLoader(int id, Bundle args) @@ -191,23 +190,23 @@ public class VpnProfileImportActivity extends AppCompatActivity setContentView(R.layout.profile_import_view); mProgressBar = findViewById(R.id.progress_bar); - mExistsWarning = (TextView)findViewById(R.id.exists_warning); - mBasicDataGroup = (ViewGroup)findViewById(R.id.basic_data_group); - mName = (TextView)findViewById(R.id.name); - mGateway = (TextView)findViewById(R.id.gateway); - mSelectVpnType = (TextView)findViewById(R.id.vpn_type); + mExistsWarning = findViewById(R.id.exists_warning); + mBasicDataGroup = findViewById(R.id.basic_data_group); + mName = findViewById(R.id.name); + mGateway = findViewById(R.id.gateway); + mSelectVpnType = findViewById(R.id.vpn_type); - mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group); - mUsername = (EditText)findViewById(R.id.username); - mUsernameWrap = (TextInputLayoutHelper) findViewById(R.id.username_wrap); - mPassword = (EditText)findViewById(R.id.password); + mUsernamePassword = findViewById(R.id.username_password_group); + mUsername = findViewById(R.id.username); + mUsernameWrap = findViewById(R.id.username_wrap); + mPassword = findViewById(R.id.password); - mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group); - mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate); - mImportUserCert = (Button)findViewById(R.id.import_user_certificate); + mUserCertificate = findViewById(R.id.user_certificate_group); + mSelectUserCert = findViewById(R.id.select_user_certificate); + mImportUserCert = findViewById(R.id.import_user_certificate); - mRemoteCertificate = (ViewGroup)findViewById(R.id.remote_certificate_group); - mRemoteCert = (RelativeLayout)findViewById(R.id.remote_certificate); + mRemoteCertificate = findViewById(R.id.remote_certificate_group); + mRemoteCert = findViewById(R.id.remote_certificate); mExistsWarning.setVisibility(View.GONE); mBasicDataGroup.setVisibility(View.GONE); @@ -216,7 +215,8 @@ public class VpnProfileImportActivity extends AppCompatActivity mRemoteCertificate.setVisibility(View.GONE); mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener()); - mImportUserCert.setOnClickListener(new View.OnClickListener() { + mImportUserCert.setOnClickListener(new View.OnClickListener() + { @Override public void onClick(View v) { @@ -233,7 +233,7 @@ public class VpnProfileImportActivity extends AppCompatActivity { loadProfile(getIntent().getData()); } - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + else { Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); openIntent.setType("*/*"); @@ -535,9 +535,9 @@ public class VpnProfileImportActivity extends AppCompatActivity if (split != null) { String included = getSubnets(split, "subnets"); - profile.setIncludedSubnets(included != null ? included : null); + profile.setIncludedSubnets(included); String excluded = getSubnets(split, "excluded"); - profile.setExcludedSubnets(excluded != null ? excluded : null); + profile.setExcludedSubnets(excluded); int st = 0; st |= split.optBoolean("block-ipv4") ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0; st |= split.optBoolean("block-ipv6") ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0; @@ -710,6 +710,7 @@ public class VpnProfileImportActivity extends AppCompatActivity /** * Verify the user input and display error messages. + * * @return true if the input is valid */ private boolean verifyInput() @@ -899,14 +900,15 @@ public class VpnProfileImportActivity extends AppCompatActivity public void alias(final String alias) { /* alias() is not called from our main thread */ - runOnUiThread(new Runnable() { + runOnUiThread(new Runnable() + { @Override public void run() { mUserCertLoading = alias; updateUserCertView(); if (alias != null) - { /* otherwise the dialog was canceled, the request denied */ + { /* otherwise the dialog was canceled, the request denied */ LoaderManager.getInstance(VpnProfileImportActivity.this).restartLoader(USER_CERT_LOADER, null, mUserCertificateLoaderCallbacks); } } From 73af77709a07252dcc15ed8b414ea896ed18af7f Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:21 +0100 Subject: [PATCH 04/45] android: Remove unnecessary @TargetApi The minSdkVersion is 21, remove unnecessary @TargetApi annotations. --- .../java/org/strongswan/android/logic/CharonVpnService.java | 1 - .../android/ui/TrustedCertificateImportActivity.java | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java index 03102502d..6bd5e6bc4 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java @@ -1236,7 +1236,6 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe } } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void applyData(VpnService.Builder builder) { for (IPRange address : mAddresses) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java index 4e1e39e2d..60e57b03b 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java @@ -16,13 +16,11 @@ package org.strongswan.android.ui; -import android.annotation.TargetApi; import android.app.Dialog; import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.widget.Toast; @@ -61,7 +59,6 @@ public class TrustedCertificateImportActivity extends AppCompatActivity } ); - @TargetApi(Build.VERSION_CODES.KITKAT) @Override public void onCreate(Bundle savedInstanceState) { From b687f0c22fefffc987675d562af1acf5d0236b09 Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:21 +0100 Subject: [PATCH 05/45] android: Use try-with-resources for IO --- .../java/org/strongswan/android/logic/CharonVpnService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java index 6bd5e6bc4..5f5b2a187 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java @@ -1037,9 +1037,8 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe @Override public synchronized void run() { - try + try (FileInputStream plain = new FileInputStream(mFd.getFileDescriptor())) { - FileInputStream plain = new FileInputStream(mFd.getFileDescriptor()); ByteBuffer packet = ByteBuffer.allocate(mCache.mMtu); while (true) { @@ -1073,7 +1072,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe } } } - catch (ClosedByInterruptException | InterruptedException e) + catch (final ClosedByInterruptException | InterruptedException e) { /* regular interruption */ } From 7c8773dea51f36c999eea9c7d3a1a34d238abeae Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:21 +0100 Subject: [PATCH 06/45] android: Add interface for VPN data source Change VPN profile source to an interface. Preparation to allow managed configurations as a second source. --- .../android/data/VpnProfileDataSource.java | 454 ++---------------- .../android/data/VpnProfileSource.java | 114 +++++ .../android/data/VpnProfileSqlDataSource.java | 433 +++++++++++++++++ .../android/logic/CharonVpnService.java | 3 +- .../android/ui/SettingsFragment.java | 16 +- .../android/ui/VpnProfileControlActivity.java | 12 +- .../android/ui/VpnProfileDetailActivity.java | 3 +- .../android/ui/VpnProfileImportActivity.java | 3 +- .../android/ui/VpnProfileListFragment.java | 34 +- .../strongswan/android/ui/VpnTileService.java | 3 +- 10 files changed, 631 insertions(+), 444 deletions(-) create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java index 5604f67a1..07308956c 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 Relution GmbH * Copyright (C) 2012-2019 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager @@ -18,298 +19,50 @@ package org.strongswan.android.data; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.util.Log; -import java.util.ArrayList; import java.util.List; import java.util.UUID; -public class VpnProfileDataSource +public interface VpnProfileDataSource { - private static final String TAG = VpnProfileDataSource.class.getSimpleName(); - public static final String KEY_ID = "_id"; - public static final String KEY_UUID = "_uuid"; - public static final String KEY_NAME = "name"; - public static final String KEY_GATEWAY = "gateway"; - public static final String KEY_VPN_TYPE = "vpn_type"; - public static final String KEY_USERNAME = "username"; - public static final String KEY_PASSWORD = "password"; - public static final String KEY_CERTIFICATE = "certificate"; - public static final String KEY_USER_CERTIFICATE = "user_certificate"; - public static final String KEY_MTU = "mtu"; - public static final String KEY_PORT = "port"; - public static final String KEY_SPLIT_TUNNELING = "split_tunneling"; - public static final String KEY_LOCAL_ID = "local_id"; - public static final String KEY_REMOTE_ID = "remote_id"; - public static final String KEY_EXCLUDED_SUBNETS = "excluded_subnets"; - public static final String KEY_INCLUDED_SUBNETS = "included_subnets"; - public static final String KEY_SELECTED_APPS = "selected_apps"; - public static final String KEY_SELECTED_APPS_LIST = "selected_apps_list"; - public static final String KEY_NAT_KEEPALIVE = "nat_keepalive"; - public static final String KEY_FLAGS = "flags"; - public static final String KEY_IKE_PROPOSAL = "ike_proposal"; - public static final String KEY_ESP_PROPOSAL = "esp_proposal"; - public static final String KEY_DNS_SERVERS = "dns_servers"; - - private DatabaseHelper mDbHelper; - private SQLiteDatabase mDatabase; - private final Context mContext; - - private static final String DATABASE_NAME = "strongswan.db"; - private static final String TABLE_VPNPROFILE = "vpnprofile"; - - private static final int DATABASE_VERSION = 17; - - public static final DbColumn[] COLUMNS = new DbColumn[] { - new DbColumn(KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 1), - new DbColumn(KEY_UUID, "TEXT UNIQUE", 9), - new DbColumn(KEY_NAME, "TEXT NOT NULL", 1), - new DbColumn(KEY_GATEWAY, "TEXT NOT NULL", 1), - new DbColumn(KEY_VPN_TYPE, "TEXT NOT NULL", 3), - new DbColumn(KEY_USERNAME, "TEXT", 1), - new DbColumn(KEY_PASSWORD, "TEXT", 1), - new DbColumn(KEY_CERTIFICATE, "TEXT", 1), - new DbColumn(KEY_USER_CERTIFICATE, "TEXT", 2), - new DbColumn(KEY_MTU, "INTEGER", 5), - new DbColumn(KEY_PORT, "INTEGER", 5), - new DbColumn(KEY_SPLIT_TUNNELING, "INTEGER", 7), - new DbColumn(KEY_LOCAL_ID, "TEXT", 8), - new DbColumn(KEY_REMOTE_ID, "TEXT", 8), - new DbColumn(KEY_EXCLUDED_SUBNETS, "TEXT", 10), - new DbColumn(KEY_INCLUDED_SUBNETS, "TEXT", 11), - new DbColumn(KEY_SELECTED_APPS, "INTEGER", 12), - new DbColumn(KEY_SELECTED_APPS_LIST, "TEXT", 12), - new DbColumn(KEY_NAT_KEEPALIVE, "INTEGER", 13), - new DbColumn(KEY_FLAGS, "INTEGER", 14), - new DbColumn(KEY_IKE_PROPOSAL, "TEXT", 15), - new DbColumn(KEY_ESP_PROPOSAL, "TEXT", 15), - new DbColumn(KEY_DNS_SERVERS, "TEXT", 17), - }; - - private static final String[] ALL_COLUMNS = getColumns(DATABASE_VERSION); - - private static String getDatabaseCreate(int version) - { - boolean first = true; - StringBuilder create = new StringBuilder("CREATE TABLE "); - create.append(TABLE_VPNPROFILE); - create.append(" ("); - for (DbColumn column : COLUMNS) - { - if (column.Since <= version) - { - if (!first) - { - create.append(","); - } - first = false; - create.append(column.Name); - create.append(" "); - create.append(column.Type); - } - } - create.append(");"); - return create.toString(); - } - - private static String[] getColumns(int version) - { - ArrayList columns = new ArrayList<>(); - for (DbColumn column : COLUMNS) - { - if (column.Since <= version) - { - columns.add(column.Name); - } - } - return columns.toArray(new String[0]); - } - - private static class DatabaseHelper extends SQLiteOpenHelper - { - public DatabaseHelper(Context context) - { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase database) - { - database.execSQL(getDatabaseCreate(DATABASE_VERSION)); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) - { - Log.w(TAG, "Upgrading database from version " + oldVersion + - " to " + newVersion); - if (oldVersion < 2) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE + - " TEXT;"); - } - if (oldVersion < 3) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE + - " TEXT DEFAULT '';"); - } - if (oldVersion < 4) - { /* remove NOT NULL constraint from username column */ - updateColumns(db, 4); - } - if (oldVersion < 5) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU + - " INTEGER;"); - } - if (oldVersion < 6) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT + - " INTEGER;"); - } - if (oldVersion < 7) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING + - " INTEGER;"); - } - if (oldVersion < 8) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_LOCAL_ID + - " TEXT;"); - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_REMOTE_ID + - " TEXT;"); - } - if (oldVersion < 9) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_UUID + - " TEXT;"); - updateColumns(db, 9); - } - if (oldVersion < 10) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_EXCLUDED_SUBNETS + - " TEXT;"); - } - if (oldVersion < 11) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_INCLUDED_SUBNETS + - " TEXT;"); - } - if (oldVersion < 12) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS + - " INTEGER;"); - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS_LIST + - " TEXT;"); - } - if (oldVersion < 13) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_NAT_KEEPALIVE + - " INTEGER;"); - } - if (oldVersion < 14) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_FLAGS + - " INTEGER;"); - } - if (oldVersion < 15) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_IKE_PROPOSAL + - " TEXT;"); - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_ESP_PROPOSAL + - " TEXT;"); - } - if (oldVersion < 16) - { /* add a UUID to all entries that haven't one yet */ - db.beginTransaction(); - try - { - Cursor cursor = db.query(TABLE_VPNPROFILE, getColumns(16), KEY_UUID + " is NULL", null, null, null, null); - for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) - { - ContentValues values = new ContentValues(); - values.put(KEY_UUID, UUID.randomUUID().toString()); - db.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID)), null); - } - cursor.close(); - db.setTransactionSuccessful(); - } - finally - { - db.endTransaction(); - } - } - if (oldVersion < 17) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_DNS_SERVERS + - " TEXT;"); - } - } - - private void updateColumns(SQLiteDatabase db, int version) - { - db.beginTransaction(); - try - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";"); - db.execSQL(getDatabaseCreate(version)); - StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT "); - SQLiteQueryBuilder.appendColumns(insert, getColumns(version)); - db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString()); - db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";"); - db.setTransactionSuccessful(); - } - finally - { - db.endTransaction(); - } - } - } - - /** - * Construct a new VPN profile data source. The context is used to - * open/create the database. - * @param context context used to access the database - */ - public VpnProfileDataSource(Context context) - { - this.mContext = context; - } + String KEY_ID = "_id"; + String KEY_UUID = "_uuid"; + String KEY_NAME = "name"; + String KEY_GATEWAY = "gateway"; + String KEY_VPN_TYPE = "vpn_type"; + String KEY_USERNAME = "username"; + String KEY_PASSWORD = "password"; + String KEY_CERTIFICATE = "certificate"; + String KEY_USER_CERTIFICATE = "user_certificate"; + String KEY_MTU = "mtu"; + String KEY_PORT = "port"; + String KEY_SPLIT_TUNNELING = "split_tunneling"; + String KEY_LOCAL_ID = "local_id"; + String KEY_REMOTE_ID = "remote_id"; + String KEY_EXCLUDED_SUBNETS = "excluded_subnets"; + String KEY_INCLUDED_SUBNETS = "included_subnets"; + String KEY_SELECTED_APPS = "selected_apps"; + String KEY_SELECTED_APPS_LIST = "selected_apps_list"; + String KEY_NAT_KEEPALIVE = "nat_keepalive"; + String KEY_FLAGS = "flags"; + String KEY_IKE_PROPOSAL = "ike_proposal"; + String KEY_ESP_PROPOSAL = "esp_proposal"; + String KEY_DNS_SERVERS = "dns_servers"; /** * Open the VPN profile data source. The database is automatically created * if it does not yet exist. If that fails an exception is thrown. + * * @return itself (allows to chain initialization calls) * @throws SQLException if the database could not be opened or created */ - public VpnProfileDataSource open() throws SQLException - { - if (mDbHelper == null) - { - mDbHelper = new DatabaseHelper(mContext); - mDatabase = mDbHelper.getWritableDatabase(); - } - return this; - } + VpnProfileDataSource open() throws SQLException; /** * Close the data source. */ - public void close() - { - if (mDbHelper != null) - { - mDbHelper.close(); - mDbHelper = null; - } - } + void close(); /** * Insert the given VPN profile into the database. On success the Id of @@ -318,83 +71,47 @@ public class VpnProfileDataSource * @param profile the profile to add * @return the added VPN profile or null, if failed */ - public VpnProfile insertProfile(VpnProfile profile) - { - ContentValues values = ContentValuesFromVpnProfile(profile); - long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values); - if (insertId == -1) - { - return null; - } - profile.setId(insertId); - return profile; - } + VpnProfile insertProfile(VpnProfile profile); /** * Updates the given VPN profile in the database. + * * @param profile the profile to update * @return true if update succeeded, false otherwise */ - public boolean updateVpnProfile(VpnProfile profile) - { - long id = profile.getId(); - ContentValues values = ContentValuesFromVpnProfile(profile); - return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0; - } + boolean updateVpnProfile(VpnProfile profile); /** * Delete the given VPN profile from the database. + * * @param profile the profile to delete * @return true if deleted, false otherwise */ - public boolean deleteVpnProfile(VpnProfile profile) - { - long id = profile.getId(); - return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0; - } + boolean deleteVpnProfile(VpnProfile profile); /** * Get a single VPN profile from the database. + * * @param id the ID of the VPN profile * @return the profile or null, if not found */ - public VpnProfile getVpnProfile(long id) - { - VpnProfile profile = null; - Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, - KEY_ID + "=" + id, null, null, null, null); - if (cursor.moveToFirst()) - { - profile = VpnProfileFromCursor(cursor); - } - cursor.close(); - return profile; - } + VpnProfile getVpnProfile(long id); /** * Get a single VPN profile from the database by its UUID. + * * @param uuid the UUID of the VPN profile * @return the profile or null, if not found */ - public VpnProfile getVpnProfile(UUID uuid) - { - VpnProfile profile = null; - Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, - KEY_UUID + "='" + uuid.toString() + "'", null, null, null, null); - if (cursor.moveToFirst()) - { - profile = VpnProfileFromCursor(cursor); - } - cursor.close(); - return profile; - } + VpnProfile getVpnProfile(UUID uuid); /** * Get a single VPN profile from the database by its UUID as String. + * * @param uuid the UUID of the VPN profile as String * @return the profile or null, if not found */ - public VpnProfile getVpnProfile(String uuid) + default VpnProfile getVpnProfile(String uuid) { try { @@ -413,97 +130,8 @@ public class VpnProfileDataSource /** * Get a list of all VPN profiles stored in the database. + * * @return list of VPN profiles */ - public List getAllVpnProfiles() - { - List vpnProfiles = new ArrayList(); - - Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null); - cursor.moveToFirst(); - while (!cursor.isAfterLast()) - { - VpnProfile vpnProfile = VpnProfileFromCursor(cursor); - vpnProfiles.add(vpnProfile); - cursor.moveToNext(); - } - cursor.close(); - return vpnProfiles; - } - - private VpnProfile VpnProfileFromCursor(Cursor cursor) - { - VpnProfile profile = new VpnProfile(); - profile.setId(cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID))); - profile.setUUID(UUID.fromString(cursor.getString(cursor.getColumnIndexOrThrow(KEY_UUID)))); - profile.setName(cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME))); - profile.setGateway(cursor.getString(cursor.getColumnIndexOrThrow(KEY_GATEWAY))); - profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndexOrThrow(KEY_VPN_TYPE)))); - profile.setUsername(cursor.getString(cursor.getColumnIndexOrThrow(KEY_USERNAME))); - profile.setPassword(cursor.getString(cursor.getColumnIndexOrThrow(KEY_PASSWORD))); - profile.setCertificateAlias(cursor.getString(cursor.getColumnIndexOrThrow(KEY_CERTIFICATE))); - profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndexOrThrow(KEY_USER_CERTIFICATE))); - profile.setMTU(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_MTU))); - profile.setPort(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_PORT))); - profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_SPLIT_TUNNELING))); - profile.setLocalId(cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCAL_ID))); - profile.setRemoteId(cursor.getString(cursor.getColumnIndexOrThrow(KEY_REMOTE_ID))); - profile.setExcludedSubnets(cursor.getString(cursor.getColumnIndexOrThrow(KEY_EXCLUDED_SUBNETS))); - profile.setIncludedSubnets(cursor.getString(cursor.getColumnIndexOrThrow(KEY_INCLUDED_SUBNETS))); - profile.setSelectedAppsHandling(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_SELECTED_APPS))); - profile.setSelectedApps(cursor.getString(cursor.getColumnIndexOrThrow(KEY_SELECTED_APPS_LIST))); - profile.setNATKeepAlive(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_NAT_KEEPALIVE))); - profile.setFlags(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_FLAGS))); - profile.setIkeProposal(cursor.getString(cursor.getColumnIndexOrThrow(KEY_IKE_PROPOSAL))); - profile.setEspProposal(cursor.getString(cursor.getColumnIndexOrThrow(KEY_ESP_PROPOSAL))); - profile.setDnsServers(cursor.getString(cursor.getColumnIndexOrThrow(KEY_DNS_SERVERS))); - return profile; - } - - private ContentValues ContentValuesFromVpnProfile(VpnProfile profile) - { - ContentValues values = new ContentValues(); - values.put(KEY_UUID, profile.getUUID().toString()); - values.put(KEY_NAME, profile.getName()); - values.put(KEY_GATEWAY, profile.getGateway()); - values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier()); - values.put(KEY_USERNAME, profile.getUsername()); - values.put(KEY_PASSWORD, profile.getPassword()); - values.put(KEY_CERTIFICATE, profile.getCertificateAlias()); - values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias()); - values.put(KEY_MTU, profile.getMTU()); - values.put(KEY_PORT, profile.getPort()); - values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling()); - values.put(KEY_LOCAL_ID, profile.getLocalId()); - values.put(KEY_REMOTE_ID, profile.getRemoteId()); - values.put(KEY_EXCLUDED_SUBNETS, profile.getExcludedSubnets()); - values.put(KEY_INCLUDED_SUBNETS, profile.getIncludedSubnets()); - values.put(KEY_SELECTED_APPS, profile.getSelectedAppsHandling().getValue()); - values.put(KEY_SELECTED_APPS_LIST, profile.getSelectedApps()); - values.put(KEY_NAT_KEEPALIVE, profile.getNATKeepAlive()); - values.put(KEY_FLAGS, profile.getFlags()); - values.put(KEY_IKE_PROPOSAL, profile.getIkeProposal()); - values.put(KEY_ESP_PROPOSAL, profile.getEspProposal()); - values.put(KEY_DNS_SERVERS, profile.getDnsServers()); - return values; - } - - private Integer getInt(Cursor cursor, int columnIndex) - { - return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex); - } - - private static class DbColumn - { - public final String Name; - public final String Type; - public final Integer Since; - - public DbColumn(String name, String type, Integer since) - { - Name = name; - Type = type; - Since = since; - } - } + List getAllVpnProfiles(); } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java new file mode 100644 index 000000000..98e879452 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 Relution GmbH + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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 General Public License + * for more details. + */ + +package org.strongswan.android.data; + +import android.content.Context; +import android.database.SQLException; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class VpnProfileSource implements VpnProfileDataSource +{ + private final List dataSources = new ArrayList<>(); + private final VpnProfileSqlDataSource vpnProfileSqlDataSource; + + public VpnProfileSource(Context context) + { + vpnProfileSqlDataSource = new VpnProfileSqlDataSource(context); + dataSources.add(vpnProfileSqlDataSource); + } + + @Override + public VpnProfileDataSource open() throws SQLException + { + for (final VpnProfileDataSource source : dataSources) + { + source.open(); + } + return this; + } + + @Override + public void close() + { + for (final VpnProfileDataSource source : dataSources) + { + source.close(); + } + } + + @Override + public VpnProfile insertProfile(VpnProfile profile) + { + return vpnProfileSqlDataSource.insertProfile(profile); + } + + @Override + public boolean updateVpnProfile(VpnProfile profile) + { + return vpnProfileSqlDataSource.updateVpnProfile(profile); + } + + @Override + public boolean deleteVpnProfile(VpnProfile profile) + { + return vpnProfileSqlDataSource.deleteVpnProfile(profile); + } + + @Override + public VpnProfile getVpnProfile(long id) + { + for (final VpnProfileDataSource source : dataSources) + { + final VpnProfile profile = source.getVpnProfile(id); + if (profile != null) + { + return profile; + } + } + return null; + } + + @Override + public VpnProfile getVpnProfile(UUID uuid) + { + for (final VpnProfileDataSource source : dataSources) + { + final VpnProfile profile = source.getVpnProfile(uuid); + if (profile != null) + { + return profile; + } + } + return null; + } + + @Override + public List getAllVpnProfiles() + { + final List profiles = new ArrayList<>(); + + for (final VpnProfileDataSource source : dataSources) + { + profiles.addAll(source.getAllVpnProfiles()); + } + + return profiles; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java new file mode 100644 index 000000000..3b5339e91 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2012-2019 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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 General Public License + * for more details. + */ + +package org.strongswan.android.data; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class VpnProfileSqlDataSource implements VpnProfileDataSource +{ + private static final String TAG = VpnProfileSqlDataSource.class.getSimpleName(); + + private static final DbColumn[] COLUMNS = new VpnProfileSqlDataSource.DbColumn[]{ + new VpnProfileSqlDataSource.DbColumn(KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 1), + new VpnProfileSqlDataSource.DbColumn(KEY_UUID, "TEXT UNIQUE", 9), + new VpnProfileSqlDataSource.DbColumn(KEY_NAME, "TEXT NOT NULL", 1), + new VpnProfileSqlDataSource.DbColumn(KEY_GATEWAY, "TEXT NOT NULL", 1), + new VpnProfileSqlDataSource.DbColumn(KEY_VPN_TYPE, "TEXT NOT NULL", 3), + new VpnProfileSqlDataSource.DbColumn(KEY_USERNAME, "TEXT", 1), + new VpnProfileSqlDataSource.DbColumn(KEY_PASSWORD, "TEXT", 1), + new VpnProfileSqlDataSource.DbColumn(KEY_CERTIFICATE, "TEXT", 1), + new VpnProfileSqlDataSource.DbColumn(KEY_USER_CERTIFICATE, "TEXT", 2), + new VpnProfileSqlDataSource.DbColumn(KEY_MTU, "INTEGER", 5), + new VpnProfileSqlDataSource.DbColumn(KEY_PORT, "INTEGER", 5), + new VpnProfileSqlDataSource.DbColumn(KEY_SPLIT_TUNNELING, "INTEGER", 7), + new VpnProfileSqlDataSource.DbColumn(KEY_LOCAL_ID, "TEXT", 8), + new VpnProfileSqlDataSource.DbColumn(KEY_REMOTE_ID, "TEXT", 8), + new VpnProfileSqlDataSource.DbColumn(KEY_EXCLUDED_SUBNETS, "TEXT", 10), + new VpnProfileSqlDataSource.DbColumn(KEY_INCLUDED_SUBNETS, "TEXT", 11), + new VpnProfileSqlDataSource.DbColumn(KEY_SELECTED_APPS, "INTEGER", 12), + new VpnProfileSqlDataSource.DbColumn(KEY_SELECTED_APPS_LIST, "TEXT", 12), + new VpnProfileSqlDataSource.DbColumn(KEY_NAT_KEEPALIVE, "INTEGER", 13), + new VpnProfileSqlDataSource.DbColumn(KEY_FLAGS, "INTEGER", 14), + new VpnProfileSqlDataSource.DbColumn(KEY_IKE_PROPOSAL, "TEXT", 15), + new VpnProfileSqlDataSource.DbColumn(KEY_ESP_PROPOSAL, "TEXT", 15), + new VpnProfileSqlDataSource.DbColumn(KEY_DNS_SERVERS, "TEXT", 17), + }; + + private DatabaseHelper mDbHelper; + private SQLiteDatabase mDatabase; + private final Context mContext; + + private static final String DATABASE_NAME = "strongswan.db"; + private static final String TABLE_VPNPROFILE = "vpnprofile"; + + private static final int DATABASE_VERSION = 17; + + private static final String[] ALL_COLUMNS = getColumns(DATABASE_VERSION); + + private static String getDatabaseCreate(int version) + { + boolean first = true; + StringBuilder create = new StringBuilder("CREATE TABLE "); + create.append(TABLE_VPNPROFILE); + create.append(" ("); + for (DbColumn column : COLUMNS) + { + if (column.Since <= version) + { + if (!first) + { + create.append(","); + } + first = false; + create.append(column.Name); + create.append(" "); + create.append(column.Type); + } + } + create.append(");"); + return create.toString(); + } + + private static String[] getColumns(int version) + { + ArrayList columns = new ArrayList<>(); + for (DbColumn column : COLUMNS) + { + if (column.Since <= version) + { + columns.add(column.Name); + } + } + return columns.toArray(new String[0]); + } + + private static class DatabaseHelper extends SQLiteOpenHelper + { + public DatabaseHelper(Context context) + { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase database) + { + database.execSQL(getDatabaseCreate(DATABASE_VERSION)); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) + { + Log.w(TAG, "Upgrading database from version " + oldVersion + + " to " + newVersion); + if (oldVersion < 2) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE + + " TEXT;"); + } + if (oldVersion < 3) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE + + " TEXT DEFAULT '';"); + } + if (oldVersion < 4) + { /* remove NOT NULL constraint from username column */ + updateColumns(db, 4); + } + if (oldVersion < 5) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU + + " INTEGER;"); + } + if (oldVersion < 6) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT + + " INTEGER;"); + } + if (oldVersion < 7) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING + + " INTEGER;"); + } + if (oldVersion < 8) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_LOCAL_ID + + " TEXT;"); + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_REMOTE_ID + + " TEXT;"); + } + if (oldVersion < 9) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_UUID + + " TEXT;"); + updateColumns(db, 9); + } + if (oldVersion < 10) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_EXCLUDED_SUBNETS + + " TEXT;"); + } + if (oldVersion < 11) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_INCLUDED_SUBNETS + + " TEXT;"); + } + if (oldVersion < 12) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS + + " INTEGER;"); + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS_LIST + + " TEXT;"); + } + if (oldVersion < 13) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_NAT_KEEPALIVE + + " INTEGER;"); + } + if (oldVersion < 14) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_FLAGS + + " INTEGER;"); + } + if (oldVersion < 15) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_IKE_PROPOSAL + + " TEXT;"); + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_ESP_PROPOSAL + + " TEXT;"); + } + if (oldVersion < 16) + { /* add a UUID to all entries that haven't one yet */ + db.beginTransaction(); + try + { + Cursor cursor = db.query(TABLE_VPNPROFILE, getColumns(16), KEY_UUID + " is NULL", null, null, null, null); + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) + { + ContentValues values = new ContentValues(); + values.put(KEY_UUID, UUID.randomUUID().toString()); + db.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID)), null); + } + cursor.close(); + db.setTransactionSuccessful(); + } + finally + { + db.endTransaction(); + } + } + if (oldVersion < 17) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_DNS_SERVERS + + " TEXT;"); + } + } + + private void updateColumns(SQLiteDatabase db, int version) + { + db.beginTransaction(); + try + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";"); + db.execSQL(getDatabaseCreate(version)); + StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT "); + SQLiteQueryBuilder.appendColumns(insert, getColumns(version)); + db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString()); + db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";"); + db.setTransactionSuccessful(); + } + finally + { + db.endTransaction(); + } + } + } + + /** + * Construct a new VPN profile data source. The context is used to + * open/create the database. + * + * @param context context used to access the database + */ + public VpnProfileSqlDataSource(Context context) + { + this.mContext = context; + } + + @Override + public VpnProfileDataSource open() throws SQLException + { + if (mDbHelper == null) + { + mDbHelper = new DatabaseHelper(mContext); + mDatabase = mDbHelper.getWritableDatabase(); + } + return this; + } + + @Override + public void close() + { + if (mDbHelper != null) + { + mDbHelper.close(); + mDbHelper = null; + } + } + + @Override + public VpnProfile insertProfile(VpnProfile profile) + { + ContentValues values = ContentValuesFromVpnProfile(profile); + long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values); + if (insertId == -1) + { + return null; + } + profile.setId(insertId); + return profile; + } + + @Override + public boolean updateVpnProfile(VpnProfile profile) + { + long id = profile.getId(); + ContentValues values = ContentValuesFromVpnProfile(profile); + return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0; + } + + @Override + public boolean deleteVpnProfile(VpnProfile profile) + { + long id = profile.getId(); + return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0; + } + + @Override + public VpnProfile getVpnProfile(long id) + { + VpnProfile profile = null; + Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, + KEY_ID + "=" + id, null, null, null, null); + if (cursor.moveToFirst()) + { + profile = VpnProfileFromCursor(cursor); + } + cursor.close(); + return profile; + } + + @Override + public VpnProfile getVpnProfile(UUID uuid) + { + VpnProfile profile = null; + Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, + KEY_UUID + "='" + uuid.toString() + "'", null, null, null, null); + if (cursor.moveToFirst()) + { + profile = VpnProfileFromCursor(cursor); + } + cursor.close(); + return profile; + } + + @Override + public List getAllVpnProfiles() + { + List vpnProfiles = new ArrayList(); + + Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) + { + VpnProfile vpnProfile = VpnProfileFromCursor(cursor); + vpnProfiles.add(vpnProfile); + cursor.moveToNext(); + } + cursor.close(); + return vpnProfiles; + } + + private VpnProfile VpnProfileFromCursor(Cursor cursor) + { + VpnProfile profile = new VpnProfile(); + profile.setId(cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID))); + profile.setUUID(UUID.fromString(cursor.getString(cursor.getColumnIndexOrThrow(KEY_UUID)))); + profile.setName(cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME))); + profile.setGateway(cursor.getString(cursor.getColumnIndexOrThrow(KEY_GATEWAY))); + profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndexOrThrow(KEY_VPN_TYPE)))); + profile.setUsername(cursor.getString(cursor.getColumnIndexOrThrow(KEY_USERNAME))); + profile.setPassword(cursor.getString(cursor.getColumnIndexOrThrow(KEY_PASSWORD))); + profile.setCertificateAlias(cursor.getString(cursor.getColumnIndexOrThrow(KEY_CERTIFICATE))); + profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndexOrThrow(KEY_USER_CERTIFICATE))); + profile.setMTU(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_MTU))); + profile.setPort(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_PORT))); + profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_SPLIT_TUNNELING))); + profile.setLocalId(cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCAL_ID))); + profile.setRemoteId(cursor.getString(cursor.getColumnIndexOrThrow(KEY_REMOTE_ID))); + profile.setExcludedSubnets(cursor.getString(cursor.getColumnIndexOrThrow(KEY_EXCLUDED_SUBNETS))); + profile.setIncludedSubnets(cursor.getString(cursor.getColumnIndexOrThrow(KEY_INCLUDED_SUBNETS))); + profile.setSelectedAppsHandling(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_SELECTED_APPS))); + profile.setSelectedApps(cursor.getString(cursor.getColumnIndexOrThrow(KEY_SELECTED_APPS_LIST))); + profile.setNATKeepAlive(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_NAT_KEEPALIVE))); + profile.setFlags(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_FLAGS))); + profile.setIkeProposal(cursor.getString(cursor.getColumnIndexOrThrow(KEY_IKE_PROPOSAL))); + profile.setEspProposal(cursor.getString(cursor.getColumnIndexOrThrow(KEY_ESP_PROPOSAL))); + profile.setDnsServers(cursor.getString(cursor.getColumnIndexOrThrow(KEY_DNS_SERVERS))); + return profile; + } + + private ContentValues ContentValuesFromVpnProfile(VpnProfile profile) + { + ContentValues values = new ContentValues(); + values.put(KEY_UUID, profile.getUUID().toString()); + values.put(KEY_NAME, profile.getName()); + values.put(KEY_GATEWAY, profile.getGateway()); + values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier()); + values.put(KEY_USERNAME, profile.getUsername()); + values.put(KEY_PASSWORD, profile.getPassword()); + values.put(KEY_CERTIFICATE, profile.getCertificateAlias()); + values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias()); + values.put(KEY_MTU, profile.getMTU()); + values.put(KEY_PORT, profile.getPort()); + values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling()); + values.put(KEY_LOCAL_ID, profile.getLocalId()); + values.put(KEY_REMOTE_ID, profile.getRemoteId()); + values.put(KEY_EXCLUDED_SUBNETS, profile.getExcludedSubnets()); + values.put(KEY_INCLUDED_SUBNETS, profile.getIncludedSubnets()); + values.put(KEY_SELECTED_APPS, profile.getSelectedAppsHandling().getValue()); + values.put(KEY_SELECTED_APPS_LIST, profile.getSelectedApps()); + values.put(KEY_NAT_KEEPALIVE, profile.getNATKeepAlive()); + values.put(KEY_FLAGS, profile.getFlags()); + values.put(KEY_IKE_PROPOSAL, profile.getIkeProposal()); + values.put(KEY_ESP_PROPOSAL, profile.getEspProposal()); + values.put(KEY_DNS_SERVERS, profile.getDnsServers()); + return values; + } + + private Integer getInt(Cursor cursor, int columnIndex) + { + return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex); + } + + private static class DbColumn + { + public final String Name; + public final String Type; + public final Integer Since; + + public DbColumn(String name, String type, Integer since) + { + Name = name; + Type = type; + Since = since; + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java index 5f5b2a187..115ff7cec 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java @@ -45,6 +45,7 @@ import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfile.SelectedAppsHandling; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnProfileSource; import org.strongswan.android.data.VpnType.VpnTypeFeature; import org.strongswan.android.logic.VpnStateService.ErrorState; import org.strongswan.android.logic.VpnStateService.State; @@ -196,7 +197,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe /* handler used to do changes in the main UI thread */ mHandler = new Handler(getMainLooper()); - mDataSource = new VpnProfileDataSource(this); + mDataSource = new VpnProfileSource(this); mDataSource.open(); /* use a separate thread as main thread for charon */ mConnectionHandler = new Thread(this); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java index 98e399256..7f047ce6b 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java @@ -16,6 +16,9 @@ package org.strongswan.android.ui; +import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE; +import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE_MRU; + import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; @@ -23,6 +26,7 @@ import android.os.Bundle; import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnProfileSource; import java.util.ArrayList; import java.util.Collections; @@ -34,9 +38,6 @@ import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceManager; -import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE; -import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE_MRU; - public class SettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener { private ListPreference mDefaultVPNProfile; @@ -46,7 +47,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer { setPreferencesFromResource(R.xml.settings, s); - mDefaultVPNProfile = (ListPreference)findPreference(PREF_DEFAULT_VPN_PROFILE); + mDefaultVPNProfile = findPreference(PREF_DEFAULT_VPN_PROFILE); mDefaultVPNProfile.setOnPreferenceChangeListener(this); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { @@ -59,11 +60,12 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer { super.onResume(); - VpnProfileDataSource profiles = new VpnProfileDataSource(getActivity()); + VpnProfileDataSource profiles = new VpnProfileSource(getActivity()); profiles.open(); List all = profiles.getAllVpnProfiles(); - Collections.sort(all, new Comparator() { + Collections.sort(all, new Comparator() + { @Override public int compare(VpnProfile lhs, VpnProfile rhs) { @@ -111,7 +113,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer private void setCurrentProfileName(String uuid) { - VpnProfileDataSource profiles = new VpnProfileDataSource(getActivity()); + VpnProfileDataSource profiles = new VpnProfileSource(getActivity()); profiles.open(); if (!uuid.equals(PREF_DEFAULT_VPN_PROFILE_MRU)) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java index 1913bbb5f..fb83c4e10 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java @@ -42,12 +42,12 @@ import android.widget.Toast; import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnProfileSource; import org.strongswan.android.data.VpnType.VpnTypeFeature; import org.strongswan.android.logic.VpnStateService; import org.strongswan.android.logic.VpnStateService.State; import org.strongswan.android.utils.Constants; -import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; @@ -257,6 +257,7 @@ public class VpnProfileControlActivity extends AppCompatActivity /** * Check if we have permission to display notifications to the user, if necessary, * ask the user to allow this. + * * @return true if profile can be initiated immediately */ private boolean checkNotificationPermission() @@ -274,6 +275,7 @@ public class VpnProfileControlActivity extends AppCompatActivity /** * Check if we are on the system's power whitelist, if necessary, or ask the user * to add us. + * * @return true if profile can be initiated immediately */ private boolean checkPowerWhitelist() @@ -373,7 +375,7 @@ public class VpnProfileControlActivity extends AppCompatActivity { VpnProfile profile = null; - VpnProfileDataSource dataSource = new VpnProfileDataSource(this); + VpnProfileDataSource dataSource = new VpnProfileSource(this); dataSource.open(); String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID); if (profileUUID != null) @@ -415,7 +417,7 @@ public class VpnProfileControlActivity extends AppCompatActivity String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID); if (profileUUID != null) { - VpnProfileDataSource dataSource = new VpnProfileDataSource(this); + VpnProfileDataSource dataSource = new VpnProfileSource(this); dataSource.open(); profile = dataSource.getVpnProfile(profileUUID); dataSource.close(); @@ -583,9 +585,9 @@ public class VpnProfileControlActivity extends AppCompatActivity final Bundle profileInfo = getArguments(); LayoutInflater inflater = getActivity().getLayoutInflater(); View view = inflater.inflate(R.layout.login_dialog, null); - EditText username = (EditText)view.findViewById(R.id.username); + EditText username = view.findViewById(R.id.username); username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME)); - final EditText password = (EditText)view.findViewById(R.id.password); + final EditText password = view.findViewById(R.id.password); AlertDialog.Builder adb = new AlertDialog.Builder(getActivity()); adb.setView(view); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index 85d178e52..8fd12338f 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -56,6 +56,7 @@ import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfile.SelectedAppsHandling; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnProfileSource; import org.strongswan.android.data.VpnType; import org.strongswan.android.data.VpnType.VpnTypeFeature; import org.strongswan.android.logic.StrongSwanApplication; @@ -188,7 +189,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity /* the title is set when we load the profile, if any */ getSupportActionBar().setDisplayHomeAsUpEnabled(true); - mDataSource = new VpnProfileDataSource(this); + mDataSource = new VpnProfileSource(this); mDataSource.open(); setContentView(R.layout.profile_detail_view); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java index c58365d9c..3f7c51c19 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java @@ -45,6 +45,7 @@ import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfile.SelectedAppsHandling; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnProfileSource; import org.strongswan.android.data.VpnType; import org.strongswan.android.data.VpnType.VpnTypeFeature; import org.strongswan.android.logic.TrustedCertificateManager; @@ -184,7 +185,7 @@ public class VpnProfileImportActivity extends AppCompatActivity getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - mDataSource = new VpnProfileDataSource(this); + mDataSource = new VpnProfileSource(this); mDataSource.open(); setContentView(R.layout.profile_import_view); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java index 291a4fcea..a4ed19308 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java @@ -41,6 +41,7 @@ import android.widget.Toast; import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnProfileSource; import org.strongswan.android.ui.adapter.VpnProfileAdapter; import org.strongswan.android.utils.Constants; @@ -65,12 +66,14 @@ public class VpnProfileListFragment extends Fragment private HashSet mSelected; private boolean mReadOnly; - private BroadcastReceiver mProfilesChanged = new BroadcastReceiver() + private final BroadcastReceiver mProfilesChanged = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - long id, ids[]; + long id; + long[] ids; + if ((id = intent.getLongExtra(Constants.VPN_PROFILES_SINGLE, 0)) > 0) { VpnProfile profile = mDataSource.getVpnProfile(id); @@ -104,7 +107,8 @@ public class VpnProfileListFragment extends Fragment /** * The activity containing this fragment should implement this interface */ - public interface OnVpnProfileSelectedListener { + public interface OnVpnProfileSelectedListener + { void onVpnProfileSelected(VpnProfile profile); } @@ -159,7 +163,7 @@ public class VpnProfileListFragment extends Fragment mSelected = selected != null ? new HashSet<>(selected) : new HashSet<>(); } - mDataSource = new VpnProfileDataSource(this.getActivity()); + mDataSource = new VpnProfileSource(this.getActivity()); mDataSource.open(); /* cached list of profiles used as backend for the ListView */ @@ -206,19 +210,18 @@ public class VpnProfileListFragment extends Fragment @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) + if (item.getItemId() == R.id.add_profile) { - case R.id.add_profile: - Intent connectionIntent = new Intent(getActivity(), - VpnProfileDetailActivity.class); - startActivity(connectionIntent); - return true; - default: - return super.onOptionsItemSelected(item); + Intent connectionIntent = new Intent(getActivity(), + VpnProfileDetailActivity.class); + startActivity(connectionIntent); + return true; } + return super.onOptionsItemSelected(item); } - private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() { + private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() + { @Override public void onItemClick(AdapterView a, View v, int position, long id) { @@ -229,7 +232,8 @@ public class VpnProfileListFragment extends Fragment } }; - private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() { + private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() + { private MenuItem mEditProfile; private MenuItem mCopyProfile; @@ -297,7 +301,7 @@ public class VpnProfileListFragment extends Fragment { profiles.add((VpnProfile)mListView.getItemAtPosition(position)); } - long ids[] = new long[profiles.size()]; + long[] ids = new long[profiles.size()]; for (int i = 0; i < profiles.size(); i++) { VpnProfile profile = profiles.get(i); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java index 2e128962d..6178c59ba 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java @@ -32,6 +32,7 @@ import android.service.quicksettings.TileService; import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnProfileSource; import org.strongswan.android.data.VpnType; import org.strongswan.android.logic.VpnStateService; import org.strongswan.android.utils.Constants; @@ -73,7 +74,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt context.bindService(new Intent(context, VpnStateService.class), mServiceConnection, Service.BIND_AUTO_CREATE); - mDataSource = new VpnProfileDataSource(this); + mDataSource = new VpnProfileSource(this); mDataSource.open(); } From d629e1d358e773d4edfdbe059d7aebfc9b8c6f40 Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:22 +0100 Subject: [PATCH 07/45] android: Fix version number on port column The onUpgrade method creates this column for database version 6. Update the DbColumn definition to match that version number. --- .../org/strongswan/android/data/VpnProfileSqlDataSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java index 3b5339e91..7032009ca 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java @@ -46,7 +46,7 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource new VpnProfileSqlDataSource.DbColumn(KEY_CERTIFICATE, "TEXT", 1), new VpnProfileSqlDataSource.DbColumn(KEY_USER_CERTIFICATE, "TEXT", 2), new VpnProfileSqlDataSource.DbColumn(KEY_MTU, "INTEGER", 5), - new VpnProfileSqlDataSource.DbColumn(KEY_PORT, "INTEGER", 5), + new VpnProfileSqlDataSource.DbColumn(KEY_PORT, "INTEGER", 6), new VpnProfileSqlDataSource.DbColumn(KEY_SPLIT_TUNNELING, "INTEGER", 7), new VpnProfileSqlDataSource.DbColumn(KEY_LOCAL_ID, "TEXT", 8), new VpnProfileSqlDataSource.DbColumn(KEY_REMOTE_ID, "TEXT", 8), From 8e3b921abed7eefcd55e91a42981260f3345a68c Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:22 +0100 Subject: [PATCH 08/45] android: Always use UUID to access profiles Use the UUID rather than the ID to ensure there are no conflicts between profiles from the database and managed profiles. --- .../strongswan/android/data/VpnProfile.java | 23 +++++++---- .../android/data/VpnProfileDataSource.java | 8 ---- .../android/data/VpnProfileSource.java | 14 ------- .../android/data/VpnProfileSqlDataSource.java | 23 ++--------- .../android/logic/CharonVpnService.java | 2 +- .../android/logic/VpnStateService.java | 39 ++++++++++++------- .../strongswan/android/ui/MainActivity.java | 2 +- .../android/ui/VpnProfileControlActivity.java | 14 ++----- .../android/ui/VpnProfileDetailActivity.java | 22 +++++------ .../android/ui/VpnProfileImportActivity.java | 6 +-- .../android/ui/VpnProfileListFragment.java | 27 ++++++------- .../android/ui/VpnProfileSelectActivity.java | 2 +- .../strongswan/android/ui/VpnTileService.java | 4 +- 13 files changed, 80 insertions(+), 106 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java index f95082711..1d3c2bbca 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java @@ -22,6 +22,7 @@ package org.strongswan.android.data; import android.text.TextUtils; import java.util.Arrays; +import java.util.Objects; import java.util.SortedSet; import java.util.TreeSet; import java.util.UUID; @@ -339,16 +340,22 @@ public class VpnProfile implements Cloneable @Override public boolean equals(Object o) { - if (o != null && o instanceof VpnProfile) + if (o == this) { - VpnProfile other = (VpnProfile)o; - if (this.mUUID != null && other.getUUID() != null) - { - return this.mUUID.equals(other.getUUID()); - } - return this.mId == other.getId(); + return true; } - return false; + if (o == null || getClass() != o.getClass()) + { + return false; + } + VpnProfile that = (VpnProfile)o; + return Objects.equals(mUUID, that.mUUID); + } + + @Override + public int hashCode() + { + return Objects.hash(mUUID); } @Override diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java index 07308956c..f5bc692cc 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java @@ -89,14 +89,6 @@ public interface VpnProfileDataSource */ boolean deleteVpnProfile(VpnProfile profile); - /** - * Get a single VPN profile from the database. - * - * @param id the ID of the VPN profile - * @return the profile or null, if not found - */ - VpnProfile getVpnProfile(long id); - /** * Get a single VPN profile from the database by its UUID. * diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java index 98e879452..4a709989a 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java @@ -71,20 +71,6 @@ public class VpnProfileSource implements VpnProfileDataSource return vpnProfileSqlDataSource.deleteVpnProfile(profile); } - @Override - public VpnProfile getVpnProfile(long id) - { - for (final VpnProfileDataSource source : dataSources) - { - final VpnProfile profile = source.getVpnProfile(id); - if (profile != null) - { - return profile; - } - } - return null; - } - @Override public VpnProfile getVpnProfile(UUID uuid) { diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java index 7032009ca..0934edc39 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java @@ -298,30 +298,16 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource @Override public boolean updateVpnProfile(VpnProfile profile) { - long id = profile.getId(); + final UUID uuid = profile.getUUID(); ContentValues values = ContentValuesFromVpnProfile(profile); - return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0; + return mDatabase.update(TABLE_VPNPROFILE, values, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0; } @Override public boolean deleteVpnProfile(VpnProfile profile) { - long id = profile.getId(); - return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0; - } - - @Override - public VpnProfile getVpnProfile(long id) - { - VpnProfile profile = null; - Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, - KEY_ID + "=" + id, null, null, null, null); - if (cursor.moveToFirst()) - { - profile = VpnProfileFromCursor(cursor); - } - cursor.close(); - return profile; + final UUID uuid = profile.getUUID(); + return mDatabase.delete(TABLE_VPNPROFILE, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0; } @Override @@ -358,7 +344,6 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource private VpnProfile VpnProfileFromCursor(Cursor cursor) { VpnProfile profile = new VpnProfile(); - profile.setId(cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID))); profile.setUUID(UUID.fromString(cursor.getString(cursor.getColumnIndexOrThrow(KEY_UUID)))); profile.setName(cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME))); profile.setGateway(cursor.getString(cursor.getColumnIndexOrThrow(KEY_GATEWAY))); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java index 115ff7cec..03e59f7ee 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java @@ -462,7 +462,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe Intent intent = new Intent(getApplicationContext(), VpnProfileControlActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setAction(VpnProfileControlActivity.START_PROFILE); - intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString()); + intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, profile.getUUID().toString()); int flags = PendingIntent.FLAG_UPDATE_CURRENT; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java index 53c22d45a..ccce06e4c 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java @@ -55,11 +55,11 @@ public class VpnStateService extends Service private ErrorState mError = ErrorState.NO_ERROR; private ImcState mImcState = ImcState.UNKNOWN; private final LinkedList mRemediationInstructions = new LinkedList(); - private static long RETRY_INTERVAL = 1000; + private static final long RETRY_INTERVAL = 1000; /* cap the retry interval at 2 minutes */ - private static long MAX_RETRY_INTERVAL = 120000; - private static int RETRY_MSG = 1; - private RetryTimeoutProvider mTimeoutProvider = new RetryTimeoutProvider(); + private static final long MAX_RETRY_INTERVAL = 120000; + private static final int RETRY_MSG = 1; + private final RetryTimeoutProvider mTimeoutProvider = new RetryTimeoutProvider(); private long mRetryTimeout; private long mRetryIn; @@ -89,7 +89,7 @@ public class VpnStateService extends Service */ public interface VpnStateListener { - public void stateChanged(); + void stateChanged(); } /** @@ -169,6 +169,7 @@ public class VpnStateService extends Service /** * Get the total number of seconds until there is an automatic retry to reconnect. + * * @return total number of seconds until the retry */ public int getRetryTimeout() @@ -178,6 +179,7 @@ public class VpnStateService extends Service /** * Get the number of seconds until there is an automatic retry to reconnect. + * * @return number of seconds until the retry */ public int getRetryIn() @@ -283,8 +285,9 @@ public class VpnStateService extends Service /** * Connect (or reconnect) a profile + * * @param profileInfo optional profile info (basically the UUID and password), taken from the - * previous profile if null + * previous profile if null * @param fromScratch true if this is a manual retry/reconnect or a completely new connection */ public void connect(Bundle profileInfo, boolean fromScratch) @@ -330,7 +333,7 @@ public class VpnStateService extends Service Intent intent = new Intent(this, VpnProfileControlActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setAction(VpnProfileControlActivity.START_PROFILE); - intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, mProfile.getUUID().toString()); + intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, mProfile.getUUID().toString()); startActivity(intent); /* reset the retry timer immediately in case the user needs more time to enter the password */ notifyListeners(() -> { @@ -353,7 +356,8 @@ public class VpnStateService extends Service */ private void notifyListeners(final Callable change) { - mHandler.post(new Runnable() { + mHandler.post(new Runnable() + { @Override public void run() { @@ -386,7 +390,8 @@ public class VpnStateService extends Service */ public void startConnection(final VpnProfile profile) { - notifyListeners(new Callable() { + notifyListeners(new Callable() + { @Override public Boolean call() throws Exception { @@ -411,7 +416,8 @@ public class VpnStateService extends Service */ public void setState(final State state) { - notifyListeners(new Callable() { + notifyListeners(new Callable() + { @Override public Boolean call() throws Exception { @@ -438,7 +444,8 @@ public class VpnStateService extends Service */ public void setError(final ErrorState error) { - notifyListeners(new Callable() { + notifyListeners(new Callable() + { @Override public Boolean call() throws Exception { @@ -471,7 +478,8 @@ public class VpnStateService extends Service */ public void setImcState(final ImcState state) { - notifyListeners(new Callable() { + notifyListeners(new Callable() + { @Override public Boolean call() throws Exception { @@ -501,7 +509,8 @@ public class VpnStateService extends Service */ public void addRemediationInstruction(final RemediationInstruction instruction) { - mHandler.post(new Runnable() { + mHandler.post(new Runnable() + { @Override public void run() { @@ -535,7 +544,8 @@ public class VpnStateService extends Service /** * Special Handler subclass that handles the retry countdown (more accurate than CountDownTimer) */ - private static class RetryHandler extends Handler { + private static class RetryHandler extends Handler + { WeakReference mService; public RetryHandler(Looper looper, VpnStateService service) @@ -604,6 +614,7 @@ public class VpnStateService extends Service /** * Called each time a new retry timeout is started. The timeout increases until reset() is * called and the base timeout is returned again. + * * @param error Error state */ public long getTimeout(ErrorState error) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java index a48a0a886..a836ffbcc 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java @@ -115,7 +115,7 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec { Intent intent = new Intent(this, VpnProfileControlActivity.class); intent.setAction(VpnProfileControlActivity.START_PROFILE); - intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString()); + intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, profile.getUUID().toString()); startActivity(intent); } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java index fb83c4e10..d9ef939ba 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java @@ -64,7 +64,7 @@ public class VpnProfileControlActivity extends AppCompatActivity { public static final String START_PROFILE = "org.strongswan.android.action.START_PROFILE"; public static final String DISCONNECT = "org.strongswan.android.action.DISCONNECT"; - public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID"; + public static final String EXTRA_VPN_PROFILE_UUID = "org.strongswan.android.VPN_PROFILE_UUID"; private static final String WAITING_FOR_RESULT = "WAITING_FOR_RESULT"; private static final String PROFILE_NAME = "PROFILE_NAME"; @@ -377,19 +377,11 @@ public class VpnProfileControlActivity extends AppCompatActivity VpnProfileDataSource dataSource = new VpnProfileSource(this); dataSource.open(); - String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID); + String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_UUID); if (profileUUID != null) { profile = dataSource.getVpnProfile(profileUUID); } - else - { - long profileId = intent.getLongExtra(EXTRA_VPN_PROFILE_ID, 0); - if (profileId > 0) - { - profile = dataSource.getVpnProfile(profileId); - } - } dataSource.close(); if (profile != null) @@ -414,7 +406,7 @@ public class VpnProfileControlActivity extends AppCompatActivity removeFragmentByTag(DIALOG_TAG); - String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID); + String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_UUID); if (profileUUID != null) { VpnProfileDataSource dataSource = new VpnProfileSource(this); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index 8fd12338f..30787e1fd 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -88,7 +88,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; public class VpnProfileDetailActivity extends AppCompatActivity { private VpnProfileDataSource mDataSource; - private Long mId; + private String mUuid; private TrustedCertificateEntry mCertEntry; private String mUserCertLoading; private CertificateIdentitiesAdapter mSelectUserIdAdapter; @@ -380,11 +380,11 @@ public class VpnProfileDetailActivity extends AppCompatActivity } }); - mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID); - if (mId == null) + mUuid = savedInstanceState == null ? null : savedInstanceState.getString(VpnProfileDataSource.KEY_UUID); + if (mUuid == null) { Bundle extras = getIntent().getExtras(); - mId = extras == null ? null : extras.getLong(VpnProfileDataSource.KEY_ID); + mUuid = extras == null ? null : extras.getString(VpnProfileDataSource.KEY_UUID); } loadProfileData(savedInstanceState); @@ -406,9 +406,9 @@ public class VpnProfileDetailActivity extends AppCompatActivity protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - if (mId != null) + if (mUuid != null) { - outState.putLong(VpnProfileDataSource.KEY_ID, mId); + outState.putString(VpnProfileDataSource.KEY_UUID, mUuid); } if (mUserCertEntry != null) { @@ -615,10 +615,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity mDataSource.insertProfile(mProfile); } Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED); - intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getId()); + intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getUUID().toString()); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); - setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId())); + setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_UUID, mProfile.getUUID().toString())); finish(); } } @@ -757,9 +757,9 @@ public class VpnProfileDetailActivity extends AppCompatActivity Integer flags = null; getSupportActionBar().setTitle(R.string.add_profile); - if (mId != null && mId != 0) + if (mUuid != null) { - mProfile = mDataSource.getVpnProfile(mId); + mProfile = mDataSource.getVpnProfile(mUuid); if (mProfile != null) { mName.setText(mProfile.getName()); @@ -791,7 +791,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity else { Log.e(VpnProfileDetailActivity.class.getSimpleName(), - "VPN profile with id " + mId + " not found"); + "VPN profile with UUID " + mUuid + " not found"); finish(); } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java index 3f7c51c19..c62383ec0 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java @@ -675,7 +675,7 @@ public class VpnProfileImportActivity extends AppCompatActivity updateProfileData(); if (mExisting != null) { - mProfile.setId(mExisting.getId()); + mProfile.setUUID(mExisting.getUUID()); mDataSource.updateVpnProfile(mProfile); } else @@ -697,14 +697,14 @@ public class VpnProfileImportActivity extends AppCompatActivity } } Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED); - intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getId()); + intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getUUID().toString()); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); - setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId())); + setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_UUID, mProfile.getUUID().toString())); finish(); } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java index a4ed19308..7c3572325 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java @@ -49,6 +49,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.UUID; import androidx.fragment.app.Fragment; @@ -71,12 +72,12 @@ public class VpnProfileListFragment extends Fragment @Override public void onReceive(Context context, Intent intent) { - long id; - long[] ids; + String uuid; + String[] uuids; - if ((id = intent.getLongExtra(Constants.VPN_PROFILES_SINGLE, 0)) > 0) + if ((uuid = intent.getStringExtra(Constants.VPN_PROFILES_SINGLE)) != null) { - VpnProfile profile = mDataSource.getVpnProfile(id); + VpnProfile profile = mDataSource.getVpnProfile(uuid); if (profile != null) { /* in case this was an edit, we remove it first */ mVpnProfiles.remove(profile); @@ -84,15 +85,15 @@ public class VpnProfileListFragment extends Fragment mListAdapter.notifyDataSetChanged(); } } - else if ((ids = intent.getLongArrayExtra(Constants.VPN_PROFILES_MULTIPLE)) != null) + else if ((uuids = intent.getStringArrayExtra(Constants.VPN_PROFILES_MULTIPLE)) != null) { - for (long i : ids) + for (String id : uuids) { Iterator profiles = mVpnProfiles.iterator(); while (profiles.hasNext()) { VpnProfile profile = profiles.next(); - if (profile.getId() == i) + if (Objects.equals(profile.getUUID().toString(), id)) { profiles.remove(); break; @@ -272,7 +273,7 @@ public class VpnProfileListFragment extends Fragment int position = mSelected.iterator().next(); VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position); Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class); - connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId()); + connectionIntent.putExtra(VpnProfileDataSource.KEY_UUID, profile.getUUID().toString()); startActivity(connectionIntent); break; } @@ -286,11 +287,11 @@ public class VpnProfileListFragment extends Fragment mDataSource.insertProfile(profile); Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED); - intent.putExtra(Constants.VPN_PROFILES_SINGLE, profile.getId()); + intent.putExtra(Constants.VPN_PROFILES_SINGLE, profile.getUUID().toString()); LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent); Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class); - connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId()); + connectionIntent.putExtra(VpnProfileDataSource.KEY_UUID, profile.getUUID().toString()); startActivity(connectionIntent); break; } @@ -301,15 +302,15 @@ public class VpnProfileListFragment extends Fragment { profiles.add((VpnProfile)mListView.getItemAtPosition(position)); } - long[] ids = new long[profiles.size()]; + String[] uuids = new String[profiles.size()]; for (int i = 0; i < profiles.size(); i++) { VpnProfile profile = profiles.get(i); - ids[i] = profile.getId(); + uuids[i] = profile.getUUID().toString(); mDataSource.deleteVpnProfile(profile); } Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED); - intent.putExtra(Constants.VPN_PROFILES_MULTIPLE, ids); + intent.putExtra(Constants.VPN_PROFILES_MULTIPLE, uuids); LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent); Toast.makeText(VpnProfileListFragment.this.getActivity(), R.string.profiles_deleted, Toast.LENGTH_SHORT).show(); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java index 78d8a92db..dd64d0c75 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java @@ -45,7 +45,7 @@ public class VpnProfileSelectActivity extends AppCompatActivity implements OnVpn public void onVpnProfileSelected(VpnProfile profile) { Intent shortcut = new Intent(VpnProfileControlActivity.START_PROFILE); - shortcut.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString()); + shortcut.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, profile.getUUID().toString()); ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(this, profile.getUUID().toString()); builder.setIntent(shortcut); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java index 6178c59ba..c49f4ba75 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java @@ -140,7 +140,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt } else if (mDataSource != null) { /* always get the plain profile without cached password */ - profile = mDataSource.getVpnProfile(profile.getId()); + profile = mDataSource.getVpnProfile(profile.getUUID()); } /* reconnect the profile in case of an error */ if (mService.getErrorState() == VpnStateService.ErrorState.NO_ERROR) @@ -173,7 +173,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt Intent intent = new Intent(this, VpnProfileControlActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setAction(VpnProfileControlActivity.START_PROFILE); - intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString()); + intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, profile.getUUID().toString()); if (profile.getVpnType().has(VpnType.VpnTypeFeature.USER_PASS) && profile.getPassword() == null) { /* the user will have to enter the password, so collapse the drawer */ From 9618c83c03abdb398c95aaf76dd46619a44d40e5 Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:21 +0100 Subject: [PATCH 09/45] android: Add read-only flag to VpnProfile --- .../org/strongswan/android/data/VpnProfile.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java index 1d3c2bbca..9219b1001 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java @@ -48,6 +48,7 @@ public class VpnProfile implements Cloneable private VpnType mVpnType; private UUID mUUID; private long mId = -1; + private boolean mReadOnly; public enum SelectedAppsHandling { @@ -55,7 +56,7 @@ public class VpnProfile implements Cloneable SELECTED_APPS_EXCLUDE(1), SELECTED_APPS_ONLY(2); - private Integer mValue; + private final Integer mValue; SelectedAppsHandling(int value) { @@ -331,6 +332,16 @@ public class VpnProfile implements Cloneable this.mFlags = flags; } + public boolean isReadOnly() + { + return mReadOnly; + } + + public void setReadOnly(boolean readOnly) + { + this.mReadOnly = readOnly; + } + @Override public String toString() { From 3391f7a465f24e41c1ca36cfe2d1ae33a7bab7c9 Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:21 +0100 Subject: [PATCH 10/45] android: Prevent editing of read-only profiles Do not allow users to edit read-only VPN profiles, with the exception of the profile's password. --- .../android/ui/VpnProfileDetailActivity.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index 30787e1fd..6746711a5 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 Relution GmbH * Copyright (C) 2012-2020 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager @@ -787,6 +788,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity local_id = mProfile.getLocalId(); alias = mProfile.getCertificateAlias(); getSupportActionBar().setTitle(mProfile.getName()); + + setReadOnly(mProfile.isReadOnly()); } else { @@ -851,6 +854,44 @@ public class VpnProfileDetailActivity extends AppCompatActivity } } + private void setReadOnly(final boolean readOnly) + { + mName.setEnabled(!readOnly); + mGateway.setEnabled(!readOnly); + mUsername.setEnabled(!readOnly); + mRemoteId.setEnabled(!readOnly); + mLocalId.setEnabled(!readOnly); + mMTU.setEnabled(!readOnly); + mPort.setEnabled(!readOnly); + mNATKeepalive.setEnabled(!readOnly); + mIncludedSubnets.setEnabled(!readOnly); + mExcludedSubnets.setEnabled(!readOnly); + mBlockIPv4.setEnabled(!readOnly); + mBlockIPv6.setEnabled(!readOnly); + mIkeProposal.setEnabled(!readOnly); + mEspProposal.setEnabled(!readOnly); + mDnsServers.setEnabled(!readOnly); + + mSelectVpnType.setEnabled(!readOnly); + mCertReq.setEnabled(!readOnly); + mUseCrl.setEnabled(!readOnly); + mUseOcsp.setEnabled(!readOnly); + mStrictRevocation.setEnabled(!readOnly); + mRsaPss.setEnabled(!readOnly); + mIPv6Transport.setEnabled(!readOnly); + + mCheckAuto.setEnabled(!readOnly); + mSelectSelectedAppsHandling.setEnabled(!readOnly); + + findViewById(R.id.install_user_certificate).setEnabled(!readOnly); + + if (readOnly) + { + mSelectCert.setOnClickListener(null); + mSelectUserCert.setOnClickListener(null); + } + } + /** * Get the string value in the given text box or null if empty * From 150dc5ab6401062797a7dc936e35cb7c91e38d8f Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:22 +0100 Subject: [PATCH 11/45] android: Make selected apps read-only Also prevent users from changing selected apps in read-only VPN profiles. --- .../android/data/VpnProfileDataSource.java | 1 + .../ui/SelectedApplicationsListFragment.java | 9 ++++++++- .../android/ui/VpnProfileDetailActivity.java | 1 + .../adapter/SelectedApplicationsAdapter.java | 20 +++++++++++++++---- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java index f5bc692cc..48aa58c35 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java @@ -49,6 +49,7 @@ public interface VpnProfileDataSource String KEY_IKE_PROPOSAL = "ike_proposal"; String KEY_ESP_PROPOSAL = "esp_proposal"; String KEY_DNS_SERVERS = "dns_servers"; + String KEY_READ_ONLY = "read_only"; /** * Open the VPN profile data source. The database is automatically created diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java index e2d949a68..5ef78b295 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java @@ -60,9 +60,11 @@ public class SelectedApplicationsListFragment extends ListFragment implements Lo super.onViewCreated(view, savedInstanceState); setHasOptionsMenu(true); - getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + final boolean readOnly = getActivity().getIntent().getBooleanExtra(VpnProfileDataSource.KEY_READ_ONLY, false); + getListView().setChoiceMode(readOnly ? ListView.CHOICE_MODE_NONE : ListView.CHOICE_MODE_MULTIPLE); mAdapter = new SelectedApplicationsAdapter(getActivity()); + mAdapter.setReadOnly(readOnly); setListAdapter(mAdapter); setListShown(false); @@ -101,6 +103,11 @@ public class SelectedApplicationsListFragment extends ListFragment implements Lo @Override public void onListItemClick(ListView l, View v, int position, long id) { + if (mAdapter.isReadOnly()) + { + return; + } + super.onListItemClick(l, v, position, id); SelectedApplicationEntry item = (SelectedApplicationEntry)getListView().getItemAtPosition(position); item.setSelected(!item.isSelected()); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index 6746711a5..b01e67f88 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -377,6 +377,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity { Intent intent = new Intent(VpnProfileDetailActivity.this, SelectedApplicationsActivity.class); intent.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps)); + intent.putExtra(VpnProfileDataSource.KEY_READ_ONLY, mProfile.isReadOnly()); mSelectApplications.launch(intent); } }); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java index c98e4ab83..9ac720d3b 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java @@ -35,11 +35,12 @@ import java.util.List; public class SelectedApplicationsAdapter extends BaseAdapter implements Filterable { - private Context mContext; + private final Context mContext; private final Object mLock = new Object(); - private List mData; + private final List mData; private List mDataFiltered; private SelectedApplicationsFilter mFilter; + private boolean mReadOnly; public SelectedApplicationsAdapter(Context context) { @@ -100,9 +101,10 @@ public class SelectedApplicationsAdapter extends BaseAdapter implements Filterab SelectedApplicationEntry item = getItem(position); CheckableLinearLayout checkable = (CheckableLinearLayout)view; checkable.setChecked(item.isSelected()); - ImageView icon = (ImageView)view.findViewById(R.id.app_icon); + checkable.setEnabled(!mReadOnly); + ImageView icon = view.findViewById(R.id.app_icon); icon.setImageDrawable(item.getIcon()); - TextView text = (TextView)view.findViewById(R.id.app_name); + TextView text = view.findViewById(R.id.app_name); text.setText(item.toString()); return view; } @@ -117,6 +119,16 @@ public class SelectedApplicationsAdapter extends BaseAdapter implements Filterab return mFilter; } + public boolean isReadOnly() + { + return mReadOnly; + } + + public void setReadOnly(final boolean readOnly) + { + this.mReadOnly = readOnly; + } + private class SelectedApplicationsFilter extends Filter { From d3f5c3a760539de24b98ecdbc0f615b0f748fbaf Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:21 +0100 Subject: [PATCH 12/45] android: Disable copy/delete for read-only profiles If a profile is marked as read-only, do not allow users to copy or delete the profile. --- .../android/ui/VpnProfileListFragment.java | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java index 7c3572325..c56962d72 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 Relution GmbH * Copyright (C) 2012-2019 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager @@ -50,6 +51,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.UUID; import androidx.fragment.app.Fragment; @@ -64,7 +66,7 @@ public class VpnProfileListFragment extends Fragment private VpnProfileAdapter mListAdapter; private ListView mListView; private OnVpnProfileSelectedListener mListener; - private HashSet mSelected; + private Set mSelected; private boolean mReadOnly; private final BroadcastReceiver mProfilesChanged = new BroadcastReceiver() @@ -237,18 +239,27 @@ public class VpnProfileListFragment extends Fragment { private MenuItem mEditProfile; private MenuItem mCopyProfile; + private MenuItem mDeleteProfile; + + private boolean mCanEdit; + private boolean mCanCopy; + private boolean mCanDelete; + + private int mReadOnlyCount; @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - mEditProfile.setEnabled(mSelected.size() == 1); - mCopyProfile.setEnabled(mEditProfile.isEnabled()); + mEditProfile.setEnabled(mCanEdit); + mCopyProfile.setEnabled(mCanCopy); + mDeleteProfile.setEnabled(mCanDelete); return true; } @Override public void onDestroyActionMode(ActionMode mode) { + mReadOnlyCount = 0; mSelected.clear(); } @@ -259,6 +270,7 @@ public class VpnProfileListFragment extends Fragment inflater.inflate(R.menu.profile_list_context, menu); mEditProfile = menu.findItem(R.id.edit_profile); mCopyProfile = menu.findItem(R.id.copy_profile); + mDeleteProfile = menu.findItem(R.id.delete_profile); mode.setTitle(R.string.select_profiles); return true; } @@ -327,13 +339,17 @@ public class VpnProfileListFragment extends Fragment public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { + VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position); + if (checked) { mSelected.add(position); + mReadOnlyCount += profile.isReadOnly() ? 1 : 0; } else { mSelected.remove(position); + mReadOnlyCount -= profile.isReadOnly() ? 1 : 0; } final int checkedCount = mSelected.size(); switch (checkedCount) @@ -348,6 +364,11 @@ public class VpnProfileListFragment extends Fragment mode.setSubtitle(String.format(getString(R.string.x_profiles_selected), checkedCount)); break; } + + mCanEdit = checkedCount == 1; + mCanCopy = checkedCount == 1 && mReadOnlyCount == 0; + mCanDelete = checkedCount > 0 && mReadOnlyCount == 0; + mode.invalidate(); } }; From c9c65a94c974ad8d64666c81e8d50057936a356b Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:22 +0100 Subject: [PATCH 13/45] android: Add label to read-only profiles in list Show "Managed profile" in the list of VPN profiles, to make it immediately obvious that a profile is managed/read-only. --- .../android/ui/adapter/VpnProfileAdapter.java | 13 +++--- .../src/main/res/layout/profile_list_item.xml | 40 +++++++++++++------ .../app/src/main/res/values-de/strings.xml | 1 + .../app/src/main/res/values-pl/strings.xml | 1 + .../app/src/main/res/values-ru/strings.xml | 1 + .../app/src/main/res/values-uk/strings.xml | 1 + .../src/main/res/values-zh-rCN/strings.xml | 1 + .../src/main/res/values-zh-rTW/strings.xml | 1 + .../app/src/main/res/values/strings.xml | 1 + 9 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java index 05a15464d..c2942b8a1 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java @@ -61,11 +61,13 @@ public class VpnProfileAdapter extends ArrayAdapter vpnProfileView = inflater.inflate(resource, null); } VpnProfile profile = getItem(position); - TextView tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_name); + TextView tv = vpnProfileView.findViewById(R.id.profile_item_name); tv.setText(profile.getName()); - tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway); + tv = vpnProfileView.findViewById(R.id.profile_item_managed); + tv.setVisibility(profile.isReadOnly() ? View.VISIBLE : View.GONE); + tv = vpnProfileView.findViewById(R.id.profile_item_gateway); tv.setText(getContext().getString(R.string.profile_gateway_label) + ": " + profile.getGateway()); - tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username); + tv = vpnProfileView.findViewById(R.id.profile_item_username); if (profile.getVpnType().has(VpnTypeFeature.USER_PASS)) { /* if the view is reused we make sure it is visible */ tv.setVisibility(View.VISIBLE); @@ -81,7 +83,7 @@ public class VpnProfileAdapter extends ArrayAdapter { tv.setVisibility(View.GONE); } - tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_certificate); + tv = vpnProfileView.findViewById(R.id.profile_item_certificate); if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE)) { tv.setText(getContext().getString(R.string.profile_user_certificate_label) + ": " + profile.getUserCertificateAlias()); @@ -103,7 +105,8 @@ public class VpnProfileAdapter extends ArrayAdapter private void sortItems() { - Collections.sort(this.items, new Comparator() { + Collections.sort(this.items, new Comparator() + { @Override public int compare(VpnProfile lhs, VpnProfile rhs) { diff --git a/src/frontends/android/app/src/main/res/layout/profile_list_item.xml b/src/frontends/android/app/src/main/res/layout/profile_list_item.xml index caf548858..d4d9b651e 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_list_item.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_list_item.xml @@ -17,44 +17,60 @@ for more details. --> + android:paddingTop="4dip"> + android:layout_marginStart="15dp" + android:textAppearance="?android:attr/textAppearanceMedium" + tools:text="Profile name" /> + + + android:textColor="?android:textColorSecondary" + tools:text="Server: vpn.example.com" /> + android:textColor="?android:textColorSecondary" + tools:text="Username" /> + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="?android:textColorSecondary" + tools:text="Certificate" /> diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml index 0b21fc747..19d5a1a54 100644 --- a/src/frontends/android/app/src/main/res/values-de/strings.xml +++ b/src/frontends/android/app/src/main/res/values-de/strings.xml @@ -139,6 +139,7 @@ Zertifikat aus VPN Profil importieren Zertifikat für \"%1$s\" Profil-ID + Verwaltetes Profil Ein Wert wird benötigt, um die Verbindung aufbauen zu können Bitte geben Sie Ihren Benutzernamen ein diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml index ec6ba216b..737eddf86 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -141,6 +141,7 @@ Import certificate from VPN profile Certificate for \"%1$s\" Profile ID + Managed profile A value is required to initiate the connection Wprowadź swoją nazwę użytkownika diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index 0150016b7..2bbe2b4dc 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -135,6 +135,7 @@ Import certificate from VPN profile Certificate for \"%1$s\" Profile ID + Managed profile A value is required to initiate the connection Пожалуйста введите имя пользователя diff --git a/src/frontends/android/app/src/main/res/values-uk/strings.xml b/src/frontends/android/app/src/main/res/values-uk/strings.xml index e02a640a3..3868fc475 100644 --- a/src/frontends/android/app/src/main/res/values-uk/strings.xml +++ b/src/frontends/android/app/src/main/res/values-uk/strings.xml @@ -136,6 +136,7 @@ Import certificate from VPN profile Certificate for \"%1$s\" Profile ID + Managed profile A value is required to initiate the connection Введіть ім\'я користувача diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml index 99693c509..69b9f4d26 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml @@ -135,6 +135,7 @@ 从VPN配置导入证书 \"%1$s\" 所对应的证书 配置文件ID + Managed profile 必填信息以初始化连接 请输入您的用户名 diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml index 79d3e41ba..33473b63e 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml @@ -135,6 +135,7 @@ 從VPN設定檔匯入憑證 \"%1$s\" 對應的憑證 Profile ID + Managed profile 請填寫必要訊息才能初始化連線 請輸入您的用戶名稱 diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index 59f24e7e2..d6d2ce854 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -139,6 +139,7 @@ Import certificate from VPN profile Certificate for \"%1$s\" Profile ID + Managed profile A value is required to initiate the connection Please enter your username From 5f9f279a33b8cf8f1efdfbb636815206f9a676ce Mon Sep 17 00:00:00 2001 From: Markus Pfeiffer Date: Tue, 21 Nov 2023 15:37:22 +0100 Subject: [PATCH 14/45] android: Show warning message for read-only profiles in detail view Show a message explaining that a managed profile can't be edited in its detail view. --- .../android/ui/VpnProfileDetailActivity.java | 5 + .../main/res/layout/profile_detail_view.xml | 998 +++++++++--------- .../app/src/main/res/values-de/strings.xml | 1 + .../app/src/main/res/values-pl/strings.xml | 1 + .../app/src/main/res/values-ru/strings.xml | 1 + .../app/src/main/res/values-uk/strings.xml | 1 + .../src/main/res/values-zh-rCN/strings.xml | 1 + .../src/main/res/values-zh-rTW/strings.xml | 1 + .../app/src/main/res/values/strings.xml | 1 + 9 files changed, 524 insertions(+), 486 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index b01e67f88..4a6aac45f 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -98,6 +98,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity private SelectedAppsHandling mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE; private SortedSet mSelectedApps = new TreeSet<>(); private VpnProfile mProfile; + private View mManagedProfile; private MultiAutoCompleteTextView mName; private TextInputLayoutHelper mNameWrap; private EditText mGateway; @@ -195,6 +196,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity setContentView(R.layout.profile_detail_view); + mManagedProfile = findViewById(R.id.managed_profile); + mName = findViewById(R.id.name); mNameWrap = findViewById(R.id.name_wrap); mGateway = findViewById(R.id.gateway); @@ -857,6 +860,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity private void setReadOnly(final boolean readOnly) { + mManagedProfile.setVisibility(readOnly ? View.VISIBLE : View.GONE); + mName.setEnabled(!readOnly); mGateway.setEnabled(!readOnly); mUsername.setEnabled(!readOnly); diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml index bdded4337..5197c63f7 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml @@ -16,541 +16,567 @@ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. --> - + - + android:background="@drawable/state_background" + android:drawableStart="@android:drawable/ic_dialog_alert" + android:drawablePadding="8dp" + android:padding="8dp" + android:text="@string/alert_text_vpn_profile_read_only" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textStyle="bold" + android:visibility="gone" + app:layout_constraintTop_toTopOf="parent" + tools:visibility="visible" /> - - - - - - - - - - - + + android:animateLayoutChanges="true" + android:orientation="vertical" + android:padding="10dp"> + android:layout_marginTop="6dp" + android:hint="@string/profile_gateway_label" + app:helper_text="@string/profile_gateway_hint"> - - - - - - - - - - - - - - - - - -