/*
- * Copyright (C) 2012-2016 Tobias Brunner
+ * Copyright (C) 2012-2017 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* HSR Hochschule fuer Technik Rapperswil
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
+import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.widget.MultiAutoCompleteTextView;
import android.widget.RelativeLayout;
import android.widget.Spinner;
+import android.widget.Switch;
import android.widget.TextView;
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.VpnType;
import org.strongswan.android.data.VpnType.VpnTypeFeature;
import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter;
import org.strongswan.android.ui.widget.TextInputLayoutHelper;
import org.strongswan.android.utils.Constants;
+import org.strongswan.android.utils.IPRangeSet;
+import org.strongswan.android.utils.Utils;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.SortedSet;
+import java.util.TreeSet;
import java.util.UUID;
public class VpnProfileDetailActivity extends AppCompatActivity
{
private static final int SELECT_TRUSTED_CERTIFICATE = 0;
+ private static final int SELECT_APPLICATIONS = 1;
private VpnProfileDataSource mDataSource;
private Long mId;
private String mSelectedUserId;
private TrustedCertificateEntry mUserCertEntry;
private VpnType mVpnType = VpnType.IKEV2_EAP;
+ private SelectedAppsHandling mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
+ private SortedSet<String> mSelectedApps = new TreeSet<>();
private VpnProfile mProfile;
private MultiAutoCompleteTextView mName;
private TextInputLayoutHelper mNameWrap;
private TextInputLayoutHelper mMTUWrap;
private EditText mPort;
private TextInputLayoutHelper mPortWrap;
+ private Switch mCertReq;
+ private EditText mNATKeepalive;
+ private TextInputLayoutHelper mNATKeepaliveWrap;
+ private EditText mIncludedSubnets;
+ private TextInputLayoutHelper mIncludedSubnetsWrap;
+ private EditText mExcludedSubnets;
+ private TextInputLayoutHelper mExcludedSubnetsWrap;
private CheckBox mBlockIPv4;
private CheckBox mBlockIPv6;
+ private Spinner mSelectSelectedAppsHandling;
+ private RelativeLayout mSelectApps;
+ private TextInputLayoutHelper mIkeProposalWrap;
+ private EditText mIkeProposal;
+ private TextInputLayoutHelper mEspProposalWrap;
+ private EditText mEspProposal;
+ private TextView mProfileIdLabel;
+ private TextView mProfileId;
@Override
public void onCreate(Bundle savedInstanceState)
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);
+ mCertReq = (Switch)findViewById(R.id.cert_req);
+ 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);
+ mSelectSelectedAppsHandling = (Spinner)findViewById(R.id.apps_handling);
+ mSelectApps = (RelativeLayout)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);
+ /* 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);
+
final SpaceTokenizer spaceTokenizer = new SpaceTokenizer();
mName.setTokenizer(spaceTokenizer);
mRemoteId.setTokenizer(spaceTokenizer);
mName.setAdapter(completeAdapter);
mRemoteId.setAdapter(completeAdapter);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+ {
+ 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) {}
}
});
+ mSelectSelectedAppsHandling.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
+ {
+ mSelectedAppsHandling = SelectedAppsHandling.values()[position];
+ updateAppsSelector();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent)
+ { /* should not happen */
+ mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
+ updateAppsSelector();
+ }
+ });
+
+ mSelectApps.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v)
+ {
+ Intent intent = new Intent(VpnProfileDetailActivity.this, SelectedApplicationsActivity.class);
+ intent.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
+ startActivityForResult(intent, SELECT_APPLICATIONS);
+ }
+ });
+
mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID);
if (mId == null)
{
updateCredentialView();
updateCertificateSelector();
updateAdvancedSettings();
+ updateAppsSelector();
}
@Override
{
outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias());
}
+ outState.putStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
}
@Override
updateCertificateSelector();
}
break;
+ case SELECT_APPLICATIONS:
+ if (resultCode == RESULT_OK)
+ {
+ ArrayList<String> selection = data.getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
+ mSelectedApps = new TreeSet<>(selection);
+ updateAppsSelector();
+ }
+ break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
}
+ /**
+ * Update the application selection UI
+ */
+ private void updateAppsSelector()
+ {
+ if (mSelectedAppsHandling == SelectedAppsHandling.SELECTED_APPS_DISABLE)
+ {
+ mSelectApps.setEnabled(false);
+ mSelectApps.setVisibility(View.GONE);
+
+ }
+ else
+ {
+ mSelectApps.setEnabled(true);
+ mSelectApps.setVisibility(View.VISIBLE);
+
+ ((TextView)mSelectApps.findViewById(android.R.id.text1)).setText(R.string.profile_select_apps);
+ String selected;
+ switch (mSelectedApps.size())
+ {
+ case 0:
+ selected = getString(R.string.profile_select_no_apps);
+ break;
+ case 1:
+ selected = getString(R.string.profile_select_one_app);
+ break;
+ default:
+ selected = getString(R.string.profile_select_x_apps, mSelectedApps.size());
+ break;
+ }
+ ((TextView)mSelectApps.findViewById(android.R.id.text2)).setText(selected);
+ }
+ }
+
/**
* Update the advanced settings UI depending on whether any advanced
* settings have already been made.
boolean show = mShowAdvanced.isChecked();
if (!show && mProfile != null)
{
- Integer st = mProfile.getSplitTunneling();
+ Integer st = mProfile.getSplitTunneling(), flags = mProfile.getFlags();
show = mProfile.getRemoteId() != null || mProfile.getMTU() != null ||
- mProfile.getPort() != null || (st != null && st != 0);
+ mProfile.getPort() != null || mProfile.getNATKeepAlive() != null ||
+ (flags != null && flags != 0) || (st != null && st != 0) ||
+ mProfile.getIncludedSubnets() != null || mProfile.getExcludedSubnets() != null ||
+ mProfile.getSelectedAppsHandling() != SelectedAppsHandling.SELECTED_APPS_DISABLE ||
+ mProfile.getIkeProposal() != null || mProfile.getEspProposal() != null;
}
mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE);
mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE);
+
+ if (show && mProfile == null)
+ {
+ mProfileIdLabel.setVisibility(View.GONE);
+ mProfileId.setVisibility(View.GONE);
+ }
}
/**
mMTUWrap.setError(String.format(getString(R.string.alert_text_out_of_range), Constants.MTU_MIN, Constants.MTU_MAX));
valid = false;
}
+ if (!validateSubnets(mIncludedSubnets))
+ {
+ mIncludedSubnetsWrap.setError(getString(R.string.alert_text_no_subnets));
+ valid = false;
+ }
+ if (!validateSubnets(mExcludedSubnets))
+ {
+ mExcludedSubnetsWrap.setError(getString(R.string.alert_text_no_subnets));
+ valid = false;
+ }
if (!validateInteger(mPort, 1, 65535))
{
mPortWrap.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535));
valid = false;
}
+ if (!validateInteger(mNATKeepalive, Constants.NAT_KEEPALIVE_MIN, Constants.NAT_KEEPALIVE_MAX))
+ {
+ mNATKeepaliveWrap.setError(String.format(getString(R.string.alert_text_out_of_range),
+ Constants.NAT_KEEPALIVE_MIN, Constants.NAT_KEEPALIVE_MAX));
+ valid = false;
+ }
+ if (!validateProposal(mIkeProposal, true))
+ {
+ mIkeProposalWrap.setError(getString(R.string.alert_text_no_proposal));
+ valid = false;
+ }
+ if (!validateProposal(mEspProposal, false))
+ {
+ mEspProposalWrap.setError(getString(R.string.alert_text_no_proposal));
+ valid = false;
+ }
return valid;
}
mProfile.setRemoteId(remote_id.isEmpty() ? null : remote_id);
mProfile.setMTU(getInteger(mMTU));
mProfile.setPort(getInteger(mPort));
+ mProfile.setNATKeepAlive(getInteger(mNATKeepalive));
+ int flags = 0;
+ flags |= !mCertReq.isChecked() ? VpnProfile.FLAGS_SUPPRESS_CERT_REQS : 0;
+ mProfile.setFlags(flags);
+ String included = mIncludedSubnets.getText().toString().trim();
+ mProfile.setIncludedSubnets(included.isEmpty() ? null : included);
+ String excluded = mExcludedSubnets.getText().toString().trim();
+ mProfile.setExcludedSubnets(excluded.isEmpty() ? null : excluded);
int st = 0;
st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0;
st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0;
mProfile.setSplitTunneling(st == 0 ? null : st);
+ mProfile.setSelectedAppsHandling(mSelectedAppsHandling);
+ mProfile.setSelectedApps(mSelectedApps);
+ String ike = mIkeProposal.getText().toString().trim();
+ mProfile.setIkeProposal(ike.isEmpty() ? null : ike);
+ String esp = mEspProposal.getText().toString().trim();
+ mProfile.setEspProposal(esp.isEmpty() ? null : esp);
}
/**
private void loadProfileData(Bundle savedInstanceState)
{
String useralias = null, local_id = null, alias = null;
+ Integer flags = null;
getSupportActionBar().setTitle(R.string.add_profile);
if (mId != null && mId != 0)
mRemoteId.setText(mProfile.getRemoteId());
mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null);
mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null);
+ mNATKeepalive.setText(mProfile.getNATKeepAlive() != null ? mProfile.getNATKeepAlive().toString() : null);
+ mIncludedSubnets.setText(mProfile.getIncludedSubnets());
+ mExcludedSubnets.setText(mProfile.getExcludedSubnets());
mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0);
mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null && (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0);
+ mSelectedAppsHandling = mProfile.getSelectedAppsHandling();
+ mSelectedApps = mProfile.getSelectedAppsSet();
+ mIkeProposal.setText(mProfile.getIkeProposal());
+ mEspProposal.setText(mProfile.getEspProposal());
+ mProfileId.setText(mProfile.getUUID().toString());
+ flags = mProfile.getFlags();
useralias = mProfile.getUserCertificateAlias();
local_id = mProfile.getLocalId();
alias = mProfile.getCertificateAlias();
}
mSelectVpnType.setSelection(mVpnType.ordinal());
+ mCertReq.setChecked(flags == null || (flags & VpnProfile.FLAGS_SUPPRESS_CERT_REQS) == 0);
/* check if the user selected a user certificate previously */
useralias = savedInstanceState == null ? useralias : savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
mCertEntry = null;
}
}
+
+ mSelectSelectedAppsHandling.setSelection(mSelectedAppsHandling.ordinal());
+ if (savedInstanceState != null)
+ {
+ ArrayList<String> selectedApps = savedInstanceState.getStringArrayList(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
+ mSelectedApps = new TreeSet<>(selectedApps);
+ }
}
/**
}
}
+ /**
+ * Check that the value in the given text box is a valid list of subnets/ranges
+ *
+ * @param view text box
+ */
+ private boolean validateSubnets(EditText view)
+ {
+ String value = view.getText().toString().trim();
+ return value.isEmpty() || IPRangeSet.fromString(value) != null;
+ }
+
+ /**
+ * Check that the value in the given text box is a valid proposal
+ *
+ * @param view text box
+ */
+ private boolean validateProposal(EditText view, boolean ike)
+ {
+ String value = view.getText().toString().trim();
+ return value.isEmpty() || Utils.isProposalValid(ike, value);
+ }
+
private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback
{
@Override