diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Behaviors/WebViewNavigationBehavior.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Behaviors/WebViewNavigationBehavior.cs new file mode 100644 index 000000000..07ae80120 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Behaviors/WebViewNavigationBehavior.cs @@ -0,0 +1,49 @@ +using System.Windows.Input; +using Xamarin.Forms; + +namespace eShopOnContainers.Core.Behaviors +{ + public class WebViewNavigationBehavior : Behavior + { + private VisualElement _element; + + public static readonly BindableProperty NavigateCommandProperty = + BindableProperty.Create("NavigateCommand", typeof(ICommand), + typeof(WebViewNavigationBehavior), default(ICommand), + BindingMode.OneWay, null); + + public ICommand NavigateCommand + { + get { return (ICommand)GetValue(NavigateCommandProperty); } + set { SetValue(NavigateCommandProperty, value); } + } + + protected override void OnAttachedTo(WebView bindable) + { + _element = bindable; + bindable.Navigating += OnWebViewNavigating; + bindable.BindingContextChanged += OnBindingContextChanged; + } + + protected override void OnDetachingFrom(WebView bindable) + { + _element = null; + BindingContext = null; + bindable.Navigating -= OnWebViewNavigating; + bindable.BindingContextChanged -= OnBindingContextChanged; + } + + private void OnBindingContextChanged(object sender, System.EventArgs e) + { + BindingContext = _element?.BindingContext; + } + + private void OnWebViewNavigating(object sender, WebNavigatingEventArgs e) + { + if (NavigateCommand != null && NavigateCommand.CanExecute(e.Url)) + { + NavigateCommand.Execute(e.Url); + } + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/GlobalSettings.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/GlobalSettings.cs index 264146e9f..5b3a5bbec 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/GlobalSettings.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/GlobalSettings.cs @@ -8,6 +8,10 @@ public const string BasketEndpoint = "http://104.40.62.65:5103/"; - public const string IdentityEndpoint = "http://104.40.62.65:5105/"; + public const string IdentityEndpoint = "http://104.40.62.65:5105/connect/authorize"; + + public const string LogoutEndpoint = "http://104.40.62.65:5105/connect/endsession"; + + public const string IdentityCallback = "http://localhost:5003/callback.html"; } } \ No newline at end of file diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Identity/IIdentityService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Identity/IIdentityService.cs new file mode 100644 index 000000000..4ea347b96 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Identity/IIdentityService.cs @@ -0,0 +1,8 @@ +namespace eShopOnContainers.Core.Services.Identity +{ + public interface IIdentityService + { + string CreateAuthorizeRequest(); + string DecodeToken(string token); + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Identity/IdentityService.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Identity/IdentityService.cs new file mode 100644 index 000000000..87fd53dc1 --- /dev/null +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Identity/IdentityService.cs @@ -0,0 +1,61 @@ +using IdentityModel.Client; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace eShopOnContainers.Core.Services.Identity +{ + public class IdentityService : IIdentityService + { + public string CreateAuthorizeRequest() + { + // Create URI to authorize endpoint + var authorizeRequest = + new AuthorizeRequest(GlobalSetting.IdentityEndpoint); + + // Dictionary with values for the authorize request + var dic = new Dictionary(); + dic.Add("client_id", "js"); + dic.Add("response_type", "id_token token"); + dic.Add("scope", "openid profile basket"); + + dic.Add("redirect_uri", GlobalSetting.IdentityCallback); + dic.Add("nonce", Guid.NewGuid().ToString("N")); + + // Add CSRF token to protect against cross-site request forgery attacks. + var currentCSRFToken = Guid.NewGuid().ToString("N"); + dic.Add("state", currentCSRFToken); + + var authorizeUri = authorizeRequest.Create(dic); + + return authorizeUri; + } + + public string DecodeToken(string token) + { + var parts = token.Split('.'); + + string partToConvert = parts[1]; + partToConvert = partToConvert.Replace('-', '+'); + partToConvert = partToConvert.Replace('_', '/'); + switch (partToConvert.Length % 4) + { + case 0: + break; + case 2: + partToConvert += "=="; + break; + case 3: + partToConvert += "="; + break; + } + + var partAsBytes = Convert.FromBase64String(partToConvert); + var partAsUTF8String = Encoding.UTF8.GetString(partAsBytes, 0, partAsBytes.Count()); + + return JObject.Parse(partAsUTF8String).ToString(); + } + } +} diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs index e1c28642a..e1d5faf98 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs @@ -7,6 +7,7 @@ using eShopOnContainers.Core.Services.OpenUrl; using eShopOnContainers.Core.Services.User; using eShopOnContainers.Core.Services.RequestProvider; using eShopOnContainers.Core.Services.Basket; +using eShopOnContainers.Core.Services.Identity; namespace eShopOnContainers.ViewModels.Base { @@ -37,6 +38,7 @@ namespace eShopOnContainers.ViewModels.Base RegisterSingleton(); _unityContainer.RegisterType(); _unityContainer.RegisterType(); + _unityContainer.RegisterType(); _unityContainer.RegisterType(); _unityContainer.RegisterType(); diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs index 2381d0958..62a8c8056 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs @@ -1,6 +1,8 @@ -using eShopOnContainers.Core.Services.OpenUrl; +using eShopOnContainers.Core.Services.Identity; +using eShopOnContainers.Core.Services.OpenUrl; using eShopOnContainers.Core.Validations; using eShopOnContainers.ViewModels.Base; +using IdentityModel.Client; using System; using System.Diagnostics; using System.Threading.Tasks; @@ -13,13 +15,18 @@ namespace eShopOnContainers.Core.ViewModels { private ValidatableObject _userName; private ValidatableObject _password; + private bool _isMock; private bool _isValid; + private string _loginUrl; private IOpenUrlService _openUrlService; + private IIdentityService _identityService; - public LoginViewModel(IOpenUrlService openUrlService) + public LoginViewModel(IOpenUrlService openUrlService, + IIdentityService identityService) { _openUrlService = openUrlService; + _identityService = identityService; _userName = new ValidatableObject(); _password = new ValidatableObject(); @@ -53,6 +60,19 @@ namespace eShopOnContainers.Core.ViewModels } } + public bool IsMock + { + get + { + return _isMock; + } + set + { + _isMock = value; + RaisePropertyChanged(() => IsMock); + } + } + public bool IsValid { get @@ -66,11 +86,35 @@ namespace eShopOnContainers.Core.ViewModels } } + public string LoginUrl + { + get + { + return _loginUrl; + } + set + { + _loginUrl = value; + RaisePropertyChanged(() => LoginUrl); + } + } + + public ICommand MockSignInCommand => new Command(MockSignInAsync); + public ICommand SignInCommand => new Command(SignInAsync); public ICommand RegisterCommand => new Command(Register); - private async void SignInAsync() + public ICommand NavigateCommand => new Command(NavigateAsync); + + public override Task InitializeAsync(object navigationData) + { + LoginUrl = _identityService.CreateAuthorizeRequest(); + + return base.InitializeAsync(navigationData); + } + + private async void MockSignInAsync() { IsBusy = true; IsValid = true; @@ -104,11 +148,42 @@ namespace eShopOnContainers.Core.ViewModels IsBusy = false; } + private async void SignInAsync() + { + IsBusy = true; + + await Task.Delay(500); + + IsValid = true; + + IsBusy = false; + } + private void Register() { _openUrlService.OpenUrl(GlobalSetting.RegisterWebsite); } + private async void NavigateAsync(string url) + { + if (url.Contains(GlobalSetting.IdentityCallback)) + { + // Parse response + var authResponse = new AuthorizeResponse(url); + + if (!string.IsNullOrWhiteSpace(authResponse.AccessToken)) + { + string decodedTokens = _identityService.DecodeToken(authResponse.AccessToken); + + if(decodedTokens != null) + { + await NavigationService.NavigateToAsync(); + await NavigationService.RemoveLastFromBackStackAsync(); + } + } + } + } + private bool Validate() { bool isValidUser = _userName.Validate(); diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/LoginView.xaml b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/LoginView.xaml index a078de3c6..13a8eccbe 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/LoginView.xaml +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/Views/LoginView.xaml @@ -3,7 +3,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="eShopOnContainers.Core.Views.LoginView" xmlns:animations="clr-namespace:eShopOnContainers.Core.Animations;assembly=eShopOnContainers.Core" - xmlns:triggers="clr-namespace:eShopOnContainers.Core.Triggers;assembly=eShopOnContainers.Core" + xmlns:triggers="clr-namespace:eShopOnContainers.Core.Triggers;assembly=eShopOnContainers.Core" + xmlns:behaviors="clr-namespace:eShopOnContainers.Core.Behaviors;assembly=eShopOnContainers.Core" Title="eShop on Containers"> @@ -46,6 +47,20 @@ Value="Center" /> + + @@ -65,8 +80,10 @@ + @@ -154,6 +171,22 @@ Padding="0" ColumnSpacing="0" RowSpacing="0"> + + + + + + + + + + + + + + + AddBasketButton.xaml @@ -80,6 +81,8 @@ + + @@ -186,6 +189,10 @@ ..\..\packages\Xamarin.FFImageLoading.2.2.6-pre-232\lib\portable-net45+win8+wpa81+wp8\FFImageLoading.Platform.dll True + + ..\..\packages\IdentityModel.1.13.1\lib\portable-net45+wp80+win8+wpa81\IdentityModel.dll + True + ..\..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll True diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/packages.config b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/packages.config index b7e19570b..747297c31 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Core/packages.config +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Core/packages.config @@ -2,6 +2,7 @@ + diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj index 003a04a26..2f6e003b9 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj @@ -17,7 +17,7 @@ Off Properties\AndroidManifest.xml true - v6.0 + v7.0 armeabi,armeabi-v7a,x86 diff --git a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj.bak b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj.bak index dcd1de431..003a04a26 100644 --- a/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj.bak +++ b/src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj.bak @@ -17,7 +17,7 @@ Off Properties\AndroidManifest.xml true - v7.0 + v6.0 armeabi,armeabi-v7a,x86 @@ -223,6 +223,8 @@ + + Designer @@ -332,9 +334,6 @@ - - - @@ -342,10 +341,10 @@ - + - + @@ -354,9 +353,7 @@ Este proyecto hace referencia a los paquetes NuGet que faltan en este equipo. Use la restauración de paquetes NuGet para descargarlos. Para obtener más información, consulte http://go.microsoft.com/fwlink/?LinkID=322105. El archivo que falta es {0}. - - \ No newline at end of file