diff --git a/AuthBot.sln b/AuthBot.sln index aa180de..ff15743 100644 --- a/AuthBot.sln +++ b/AuthBot.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleAADv2Bot", "SampleAAD EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OneDriveBot", "OneDriveBot\OneDriveBot.csproj", "{44BE2EEE-3F57-44D0-A298-C0967048DAE8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleVSOBot", "SampleVSOBot\SampleVSOBot.csproj", "{4EE65876-C3A8-4F9D-973A-42729E205F94}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleAADv1Bot", "SampleAADv1Bot\SampleAADv1Bot.csproj", "{D2253234-1279-486E-8A63-D8C3424E0525}" EndProject Global @@ -22,15 +24,16 @@ Global {B8AD59D3-C36D-4E18-B504-06871001BC8D}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8AD59D3-C36D-4E18-B504-06871001BC8D}.Release|Any CPU.Build.0 = Release|Any CPU {DFDC62CE-993B-461E-99DB-F079F46105C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DFDC62CE-993B-461E-99DB-F079F46105C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {DFDC62CE-993B-461E-99DB-F079F46105C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {DFDC62CE-993B-461E-99DB-F079F46105C5}.Release|Any CPU.Build.0 = Release|Any CPU {44BE2EEE-3F57-44D0-A298-C0967048DAE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44BE2EEE-3F57-44D0-A298-C0967048DAE8}.Debug|Any CPU.Build.0 = Debug|Any CPU {44BE2EEE-3F57-44D0-A298-C0967048DAE8}.Release|Any CPU.ActiveCfg = Release|Any CPU {44BE2EEE-3F57-44D0-A298-C0967048DAE8}.Release|Any CPU.Build.0 = Release|Any CPU + {4EE65876-C3A8-4F9D-973A-42729E205F94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EE65876-C3A8-4F9D-973A-42729E205F94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EE65876-C3A8-4F9D-973A-42729E205F94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EE65876-C3A8-4F9D-973A-42729E205F94}.Release|Any CPU.Build.0 = Release|Any CPU {D2253234-1279-486E-8A63-D8C3424E0525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D2253234-1279-486E-8A63-D8C3424E0525}.Debug|Any CPU.Build.0 = Debug|Any CPU {D2253234-1279-486E-8A63-D8C3424E0525}.Release|Any CPU.ActiveCfg = Release|Any CPU {D2253234-1279-486E-8A63-D8C3424E0525}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection diff --git a/AuthBot/AuthBot.csproj b/AuthBot/AuthBot.csproj index 44f6bf1..1332eec 100644 --- a/AuthBot/AuthBot.csproj +++ b/AuthBot/AuthBot.csproj @@ -55,12 +55,12 @@ ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.Platform.dll True - - ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.12.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll True - - ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.12.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + + ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll True @@ -105,11 +105,6 @@ - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - ..\packages\System.Runtime.Serialization.Primitives.4.1.1\lib\net46\System.Runtime.Serialization.Primitives.dll @@ -132,6 +127,7 @@ True + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll True @@ -157,11 +153,13 @@ + + diff --git a/AuthBot/ContextExtensions.cs b/AuthBot/ContextExtensions.cs index 5944bf9..4a6311f 100644 --- a/AuthBot/ContextExtensions.cs +++ b/AuthBot/ContextExtensions.cs @@ -18,18 +18,42 @@ public static async Task GetAccessToken(this IBotContext context, string { try { - InMemoryTokenCacheADAL tokenCache = new InMemoryTokenCacheADAL(authResult.TokenCache); - var result = await AzureActiveDirectoryHelper.GetToken(authResult.UserUniqueId, tokenCache, resourceId); - authResult.AccessToken = result.AccessToken; - authResult.ExpiresOnUtcTicks = result.ExpiresOnUtcTicks; - authResult.TokenCache = tokenCache.Serialize(); - context.StoreAuthResult(authResult); + //await context.PostAsync($"authToken={authResult.AccessToken}"); + //await context.PostAsync($"ExpiresOnUtcTicks={authResult.ExpiresOnUtcTicks}"); + if (string.Equals(authResult.authType, "vso", StringComparison.OrdinalIgnoreCase)) + { + //is Token still valid ? + if (String.IsNullOrEmpty(authResult.AccessToken)) + { await context.PostAsync("GetAccessToken called but no token exists.Logon first."); } + else if (VisualStudioOnlineHelper.IsTokenExpired(authResult.ExpiresOnUtcTicks)) + { + //renew token + DateTime expiredDate = new DateTime(authResult.ExpiresOnUtcTicks); + String expireDateStr = expiredDate.ToString("d/M/yyyy HH:mm:ss"); + await context.PostAsync($"Your VSO credentials expired on {expireDateStr}"); + authResult = await VisualStudioOnlineHelper.RefreshTokenAsync(authResult.refreshToken); + expiredDate = new DateTime(authResult.ExpiresOnUtcTicks); + expireDateStr = expiredDate.ToString("d/M/yyyy HH:mm:ss"); + await context.PostAsync($"Token refreshed. New expire time: {expireDateStr}"); + context.StoreAuthResult(authResult); + return authResult.AccessToken; + } + + } + else { + InMemoryTokenCacheADAL tokenCache = new InMemoryTokenCacheADAL(authResult.TokenCache); + var result = await AzureActiveDirectoryHelper.GetToken(authResult.UserUniqueId, tokenCache, resourceId); + authResult.AccessToken = result.AccessToken; + authResult.ExpiresOnUtcTicks = result.ExpiresOnUtcTicks; + authResult.TokenCache = tokenCache.Serialize(); + context.StoreAuthResult(authResult); + } } catch (Exception ex) { Trace.TraceError("Failed to renew token: " + ex.Message); - await context.PostAsync("Your credentials expired and could not be renewed automatically!"); - await context.Logout(); + await context.PostAsync("Exception in GetAccessToken: " + ex.Message); + //await context.Logout(); return null; } return authResult.AccessToken; @@ -81,11 +105,25 @@ public static void StoreAuthResult(this IBotContext context, AuthResult authResu public static async Task Logout(this IBotContext context) { - context.UserData.RemoveValue(ContextConstants.AuthResultKey); - context.UserData.RemoveValue(ContextConstants.MagicNumberKey); - context.UserData.RemoveValue(ContextConstants.MagicNumberValidated); - string signoutURl = "https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=" + System.Net.WebUtility.UrlEncode(AuthSettings.RedirectUrl); - await context.PostAsync($"In order to finish the sign out, please click at this [link]({signoutURl})."); + AuthResult authResult; + string signoutURl; + + if (context.UserData.TryGetValue(ContextConstants.AuthResultKey, out authResult)) + { + context.UserData.RemoveValue(ContextConstants.AuthResultKey); + context.UserData.RemoveValue(ContextConstants.MagicNumberKey); + context.UserData.RemoveValue(ContextConstants.MagicNumberValidated); + if (string.Equals(authResult.authType, "vso", StringComparison.OrdinalIgnoreCase)) + { + signoutURl = "https://app.vssps.visualstudio.com/_signout"; + } + else + { + signoutURl = "https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=" + System.Net.WebUtility.UrlEncode(AuthSettings.RedirectUrl); + } + + await context.PostAsync($"In order to finish the sign out, please click at this [link]({signoutURl})."); + } } } diff --git a/AuthBot/Controllers/OAuthCallbackController.cs b/AuthBot/Controllers/OAuthCallbackController.cs index d1c645b..956e5fc 100644 --- a/AuthBot/Controllers/OAuthCallbackController.cs +++ b/AuthBot/Controllers/OAuthCallbackController.cs @@ -72,8 +72,10 @@ public async Task OAuthCallback( { } + // Get the resumption cookie var resumptionCookie = new ResumptionCookie(queryParams["userId"], queryParams["botId"], queryParams["conversationId"], queryParams["channelId"], queryParams["serviceUrl"], queryParams["locale"]); + // Create the message that is send to conversation to resume the login flow var message = resumptionCookie.GetMessage(); @@ -96,7 +98,13 @@ public async Task OAuthCallback( else if (string.Equals(AuthSettings.Mode, "b2c", StringComparison.OrdinalIgnoreCase)) { } - + else if (string.Equals(AuthSettings.Mode, "vso", StringComparison.OrdinalIgnoreCase)) + { + // Exchange the Auth code with Access token + authResult = await VisualStudioOnlineHelper.GetTokenByAuthCodeAsync(code); + } + + IStateClient sc = scope.Resolve(); //IMPORTANT: DO NOT REMOVE THE MAGIC NUMBER CHECK THAT WE DO HERE. THIS IS AN ABSOLUTE SECURITY REQUIREMENT diff --git a/AuthBot/Dialogs/AzureAuthDialog.cs b/AuthBot/Dialogs/AzureAuthDialog.cs index 9aec1f4..4c7e76e 100644 --- a/AuthBot/Dialogs/AzureAuthDialog.cs +++ b/AuthBot/Dialogs/AzureAuthDialog.cs @@ -18,9 +18,10 @@ public class AzureAuthDialog : IDialog private string[] scopes; private string prompt; - public AzureAuthDialog(string resourceId) + public AzureAuthDialog(string resourceId, string prompt = "Please click to sign in: ") { this.resourceId = resourceId; + this.prompt = prompt; } public AzureAuthDialog(string[] scopes,string prompt="Please click to sign in: ") { @@ -101,9 +102,16 @@ private async Task LogIn(IDialogContext context, IMessageActivity msg, string[] { var resumptionCookie = new ResumptionCookie(msg); - var authenticationUrl = await AzureActiveDirectoryHelper.GetAuthUrlAsync(resumptionCookie, scopes); + String authenticationUrl; + if (string.Equals(AuthSettings.Mode, "vso", StringComparison.OrdinalIgnoreCase)) + { + authenticationUrl = VisualStudioOnlineHelper.GetAuthUrlAsync(resumptionCookie); + } + else + { authenticationUrl = await AzureActiveDirectoryHelper.GetAuthUrlAsync(resumptionCookie, scopes); } - if (msg.ChannelId == "skype" ) + //if (msg.ChannelId == "skype" && !string.Equals(AuthSettings.Mode, "vso", StringComparison.OrdinalIgnoreCase)) + if (msg.ChannelId == "skype" || msg.ChannelId == "emulator") { IMessageActivity response = context.MakeMessage(); response.Recipient = msg.From; @@ -157,7 +165,14 @@ private async Task LogIn(IDialogContext context, IMessageActivity msg, string re { var resumptionCookie = new ResumptionCookie(msg); - var authenticationUrl = await AzureActiveDirectoryHelper.GetAuthUrlAsync(resumptionCookie, resourceId); + String authenticationUrl; + if (string.Equals(AuthSettings.Mode, "vso", StringComparison.OrdinalIgnoreCase)) + { + authenticationUrl = VisualStudioOnlineHelper.GetAuthUrlAsync(resumptionCookie); + } + else + { authenticationUrl = await AzureActiveDirectoryHelper.GetAuthUrlAsync(resumptionCookie, resourceId); } + await context.PostAsync($"You must be authenticated before you can proceed. Please, click [here]({authenticationUrl}) to log into your account."); diff --git a/AuthBot/Helpers/AzureActiveDirectoryHelper.cs b/AuthBot/Helpers/AzureActiveDirectoryHelper.cs index 20e84e6..8794026 100644 --- a/AuthBot/Helpers/AzureActiveDirectoryHelper.cs +++ b/AuthBot/Helpers/AzureActiveDirectoryHelper.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. namespace AuthBot.Helpers { - using System; - using System.Text; - using System.Threading.Tasks; - using System.Web; + using System; + using System.Text; + using System.Threading.Tasks; + using System.Web; using Microsoft.Bot.Builder.Dialogs; using Models; @@ -23,8 +23,8 @@ public static async Task GetAuthUrlAsync(ResumptionCookie resumptionCook Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier.AnyUser, $"state={extraParameters}"); return uri.ToString(); - } - + } + public static async Task GetAuthUrlAsync(ResumptionCookie resumptionCookie, string[] scopes) { var extraParameters = BuildExtraParameters(resumptionCookie); @@ -90,27 +90,27 @@ public static async Task GetToken(string userUniqueId, Microsoft.Ide return authResult; } - public static string TokenEncoder(string token) - { - return HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(token)); - } - - public static string TokenDecoder(string token) - { - return Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(token)); - } - - private static string BuildExtraParameters(ResumptionCookie resumptionCookie) - { - var queryString = HttpUtility.ParseQueryString(string.Empty); - queryString["userId"] = resumptionCookie.Address.UserId; - queryString["botId"] = resumptionCookie.Address.BotId; - queryString["conversationId"] = resumptionCookie.Address.ConversationId; - queryString["serviceUrl"] = resumptionCookie.Address.ServiceUrl; - queryString["channelId"] = resumptionCookie.Address.ChannelId; - queryString["locale"] = resumptionCookie.Locale ?? "en"; - - return TokenEncoder(queryString.ToString()); + public static string TokenEncoder(string token) + { + return HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(token)); + } + + public static string TokenDecoder(string token) + { + return Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(token)); + } + + private static string BuildExtraParameters(ResumptionCookie resumptionCookie) + { + var queryString = HttpUtility.ParseQueryString(string.Empty); + queryString["userId"] = resumptionCookie.Address.UserId; + queryString["botId"] = resumptionCookie.Address.BotId; + queryString["conversationId"] = resumptionCookie.Address.ConversationId; + queryString["serviceUrl"] = resumptionCookie.Address.ServiceUrl; + queryString["channelId"] = resumptionCookie.Address.ChannelId; + queryString["locale"] = resumptionCookie.Locale ?? "en"; + + return TokenEncoder(queryString.ToString()); } } } diff --git a/AuthBot/Helpers/VisualStudioOnlineHelper.cs b/AuthBot/Helpers/VisualStudioOnlineHelper.cs new file mode 100644 index 0000000..8c96d5e --- /dev/null +++ b/AuthBot/Helpers/VisualStudioOnlineHelper.cs @@ -0,0 +1,109 @@ +using AuthBot.Models; +using Microsoft.Bot.Builder.Dialogs; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; + + +namespace AuthBot.Helpers +{ + class VisualStudioOnlineHelper + { + public static string GetAuthUrlAsync(ResumptionCookie resumptionCookie) + { + + var resumptionCookieAsParams = String.Format("userId={0}&botId={1}&conversationId={2}&channelId={3}&serviceUrl={4}&locale={5}", resumptionCookie.Address.UserId,resumptionCookie.Address.BotId, resumptionCookie.Address.ConversationId, resumptionCookie.Address.ChannelId, resumptionCookie.Address.ServiceUrl, resumptionCookie.Locale); + var encodedCookie = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(resumptionCookieAsParams)); + + var scopes = String.Join(",", AuthSettings.Scopes); + return String.Format("{0}/oauth2/authorize?client_id={1}&response_type=Assertion&state={2}&scope={3}&redirect_uri={4}", AuthSettings.EndpointUrl, AuthSettings.ClientId, encodedCookie, scopes, AuthSettings.RedirectUrl); + } + public static async Task GetTokenByAuthCodeAsync(string authorizationCode) + { + AuthResult authResult = null; + VSOAuthResult _VSOauthResult=null; + + using (var client = new HttpClient()) + { + + client.BaseAddress = new Uri("https://app.vssps.visualstudio.com"); + using (var request = new HttpRequestMessage(HttpMethod.Post, "/oauth2/token")) + { + var postData = GenerateRequestPostData(authorizationCode); + request.Content = new StringContent(postData, Encoding.UTF8, "application/x-www-form-urlencoded"); + HttpResponseMessage response = await client.SendAsync(request); + if (response.StatusCode == HttpStatusCode.OK) + { + var strWebAuthResponse = await response.Content.ReadAsStringAsync(); + + _VSOauthResult = VSOAuthResult.FromVSOAuthenticationResult(strWebAuthResponse); + authResult = AuthResult.FromVSOAuthenticationResult(_VSOauthResult); + } + } + + } + + return authResult; + } + + public static async Task RefreshTokenAsync(string refreshToken) + { + AuthResult authResult = null; + VSOAuthResult _VSOauthResult = null; + + using (var client = new HttpClient()) + { + + client.BaseAddress = new Uri("https://app.vssps.visualstudio.com"); + using (var request = new HttpRequestMessage(HttpMethod.Post, "/oauth2/token")) + { + var postData = GenerateRefreshPostData(refreshToken); + request.Content = new StringContent(postData, Encoding.UTF8, "application/x-www-form-urlencoded"); + HttpResponseMessage response = await client.SendAsync(request); + if (response.StatusCode == HttpStatusCode.OK) + { + var strWebAuthResponse = await response.Content.ReadAsStringAsync(); + + _VSOauthResult = VSOAuthResult.FromVSOAuthenticationResult(strWebAuthResponse); + authResult = AuthResult.FromVSOAuthenticationResult(_VSOauthResult); + } + } + + } + + return authResult; + } + private static string GenerateRequestPostData(string code) + { + return string.Format("client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion={0}&grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={1}&redirect_uri={2}", + HttpUtility.UrlEncode(AuthSettings.ClientSecret), + HttpUtility.UrlEncode(code), + AuthSettings.RedirectUrl + ); + } + private static string GenerateRefreshPostData(string refreshToken) + { + return string.Format("client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion={0}&grant_type=refresh_token&assertion={1}&redirect_uri={2}", + HttpUtility.UrlEncode(AuthSettings.ClientSecret), + HttpUtility.UrlEncode(refreshToken), + AuthSettings.RedirectUrl + ); + } + + + public static bool IsTokenExpired(long _expiresOnUtcTicks) { + + return DateTime.UtcNow.Ticks > _expiresOnUtcTicks; + + } + + } +} diff --git a/AuthBot/Models/AuthResult.cs b/AuthBot/Models/AuthResult.cs index c13cc61..51cb9d8 100644 --- a/AuthBot/Models/AuthResult.cs +++ b/AuthBot/Models/AuthResult.cs @@ -11,7 +11,13 @@ public class AuthResult public string UserUniqueId { get; set; } public long ExpiresOnUtcTicks { get; set; } public byte[] TokenCache { get; set; } - + + public String tokenType { get; set; } + + public String refreshToken { get; set; } + + public String authType { get; set; } //if AD or VSO + public static AuthResult FromADALAuthenticationResult(Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationResult authResult, Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache tokenCache) { var result = new AuthResult @@ -20,7 +26,10 @@ public static AuthResult FromADALAuthenticationResult(Microsoft.IdentityModel.Cl UserName = $"{authResult.UserInfo.GivenName} {authResult.UserInfo.FamilyName}", UserUniqueId = authResult.UserInfo.UniqueId, ExpiresOnUtcTicks = authResult.ExpiresOn.UtcTicks, - TokenCache = tokenCache.Serialize() + TokenCache = tokenCache.Serialize(), + tokenType = String.Empty, + refreshToken = String.Empty, + authType = "AD" }; return result; @@ -34,7 +43,31 @@ public static AuthResult FromMSALAuthenticationResult(Microsoft.Identity.Client. UserName = $"{authResult.User.Name}", UserUniqueId = authResult.User.UniqueId, ExpiresOnUtcTicks = authResult.ExpiresOn.UtcTicks, - TokenCache = tokenCache.Serialize() + TokenCache = tokenCache.Serialize(), + tokenType = String.Empty, + refreshToken = String.Empty, + + authType = "AD" + }; + + return result; + } + + public static AuthResult FromVSOAuthenticationResult(VSOAuthResult authResult) + { + + long _expiresOnUtcTicks = DateTime.UtcNow.AddSeconds(Int32.Parse(authResult.expiresIn)).Ticks; + + var result = new AuthResult + { + AccessToken = authResult.accessToken, + UserName = String.Empty, + UserUniqueId = String.Empty, + ExpiresOnUtcTicks = _expiresOnUtcTicks, + TokenCache = new byte [0], + tokenType = authResult.tokenType, + refreshToken=authResult.refreshToken, + authType = "VSO" }; return result; diff --git a/AuthBot/Models/VSOAuthResult.cs b/AuthBot/Models/VSOAuthResult.cs new file mode 100644 index 0000000..b11b788 --- /dev/null +++ b/AuthBot/Models/VSOAuthResult.cs @@ -0,0 +1,39 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AuthBot.Models +{ + + public class VSOAuthResult + { + public VSOAuthResult() { } + + [JsonProperty(PropertyName = "access_token")] + public String accessToken { get; set; } + + [JsonProperty(PropertyName = "token_type")] + public String tokenType { get; set; } + + [JsonProperty(PropertyName = "expires_in")] + public String expiresIn { get; set; } + + [JsonProperty(PropertyName = "refresh_token")] + public String refreshToken { get; set; } + public static VSOAuthResult FromVSOAuthenticationResult(String strWebAuthResponse) + { + VSOAuthResult token = new VSOAuthResult(); + token = JsonConvert.DeserializeObject(strWebAuthResponse); + + return token; + } + } + + + + + +} diff --git a/README.md b/README.md index e314680..51eaf1b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ The goals are: * Enable scenarios where bots need to communicate with other services such as Office 365 or Azure by obtaining access tokens +* Support authentication to Visual Studio Team Services (www.visualstudio.com) + ## How does it work? @@ -55,6 +57,15 @@ B2C enables users to sign up to your AAD tenant using external identity provider This model is not yet supported by this library. We're working on it. +### Visual Studio Online (VSO): +Visual Studio Online (http://www.visualstudio.com). +To authenticate with VSO you´ll need: + +1. You will register an application in https://app.vsaex.visualstudio.com/app/register. Unfortunatly currently you cannot specify localhost as the Authorization Callback URL. So you'll not be able to fully test the authentication flow with Bot Emulator. You will need to deploy to a site other than localhost. +2. During registration, you will select the scopes that your application needs. The application code itself will request for specific permissions in runtime and it needs to be a subset of the scopes defined in the registration process. The scopes are defined at logon time. +3. Look at SampleVSOBot project. Configure the appsettings area in web.config with your settings. +4. [Check here for more details.](SampleVSOBot.md) + ### Tips with setup Regardless of whether you use endpoints v1 or v2, once you register your application you need to set the redirect URI to /api/OAuthCallback. For example, if your bot will run on http://localhost:1234, then the redirect URI will have to be http://localhost:1234/api/OAuthCallback. This ensures that once the user finishes the login, your bot will become aware of it. diff --git a/SampleAADv1Bot/SampleAADv1Bot.csproj b/SampleAADv1Bot/SampleAADv1Bot.csproj index c977037..e492d7a 100644 --- a/SampleAADv1Bot/SampleAADv1Bot.csproj +++ b/SampleAADv1Bot/SampleAADv1Bot.csproj @@ -1,233 +1,233 @@ - - - - - - Debug - AnyCPU - - - 2.0 - {D2253234-1279-486E-8A63-D8C3424E0525} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - SampleAADv1Bot - SampleAADv1Bot - v4.6 - true - - - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - CS1998 - - - pdbonly - true - bin\ - TRACE - prompt - 4 - CS1998 - - - - ..\packages\AuthBot.3.1.1-alpha\lib\net40\AuthBot.dll - True - - - ..\packages\Autofac.4.1.1\lib\net45\Autofac.dll - True - - - ..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll - True - - - ..\packages\Microsoft.Bot.Builder.3.2.1\lib\net46\Microsoft.Bot.Builder.dll - True - - - ..\packages\Microsoft.Bot.Builder.3.2.1\lib\net46\Microsoft.Bot.Connector.dll - True - - - - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net46\Microsoft.Diagnostics.Tracing.EventSource.dll - True - - - ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.dll - True - - - ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.Platform.dll - True - - - ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.12.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True - - - ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.12.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True - - - ..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True - - - ..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll - True - - - ..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\packages\Microsoft.Rest.ClientRuntime.2.3.2\lib\net45\Microsoft.Rest.ClientRuntime.dll - True - - - ..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll - True - - - ..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - - - ..\packages\Newtonsoft.Json.9.0.2-beta1\lib\net45\Newtonsoft.Json.dll - True - - - - - ..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll - True - - - ..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll - True - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - - - ..\packages\System.Runtime.Serialization.Primitives.4.1.1\lib\net46\System.Runtime.Serialization.Primitives.dll - True - - - ..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll - True - - - ..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - - - ..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - - - ..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - True - - - - - - - - - - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll - True - - - - - - - - - - - - Designer - - - - - - - - - Global.asax - - - - - - - Web.config - - - Web.config - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + + + + Debug + AnyCPU + + + 2.0 + {D2253234-1279-486E-8A63-D8C3424E0525} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + SampleAADv1Bot + SampleAADv1Bot + v4.6 + true + + + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + CS1998 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + CS1998 + + + + ..\packages\AuthBot.3.1.1-alpha\lib\net40\AuthBot.dll + True + + + ..\packages\Autofac.4.1.1\lib\net45\Autofac.dll + True + + + ..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll + True + + + ..\packages\Microsoft.Bot.Builder.3.2.1\lib\net46\Microsoft.Bot.Builder.dll + True + + + ..\packages\Microsoft.Bot.Builder.3.2.1\lib\net46\Microsoft.Bot.Connector.dll + True + + + + ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net46\Microsoft.Diagnostics.Tracing.EventSource.dll + True + + + ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.dll + True + + + ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.Platform.dll + True + + + ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.12.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + True + + + ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.12.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + True + + + ..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll + True + + + ..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll + True + + + ..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll + True + + + ..\packages\Microsoft.Rest.ClientRuntime.2.3.2\lib\net45\Microsoft.Rest.ClientRuntime.dll + True + + + ..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll + True + + + ..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll + True + + + ..\packages\Newtonsoft.Json.9.0.2-beta1\lib\net45\Newtonsoft.Json.dll + True + + + + + ..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + True + + + ..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll + True + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + True + + + + + ..\packages\System.Runtime.Serialization.Primitives.4.1.1\lib\net46\System.Runtime.Serialization.Primitives.dll + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll + True + + + ..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + + + ..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + + + ..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll + True + + + + + + + + + + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll + True + + + + + + + + + + + + Designer + + + + + + + + + Global.asax + + + + + + + Web.config + + + Web.config + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + --> \ No newline at end of file diff --git a/SampleAADv1Bot/Web.config b/SampleAADv1Bot/Web.config index 837b3de..26a9562 100644 --- a/SampleAADv1Bot/Web.config +++ b/SampleAADv1Bot/Web.config @@ -1,103 +1,103 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleAADv1Bot/packages.config b/SampleAADv1Bot/packages.config index 2a7a6a1..aae022d 100644 --- a/SampleAADv1Bot/packages.config +++ b/SampleAADv1Bot/packages.config @@ -1,50 +1,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleAADv2Bot/SampleAADv2Bot.csproj b/SampleAADv2Bot/SampleAADv2Bot.csproj index 6b82768..35317e2 100644 --- a/SampleAADv2Bot/SampleAADv2Bot.csproj +++ b/SampleAADv2Bot/SampleAADv2Bot.csproj @@ -1,227 +1,227 @@ - - - - - Debug - AnyCPU - - - 2.0 - {DFDC62CE-993B-461E-99DB-F079F46105C5} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - SampleAADV2Bot - SampleAADV2Bot - v4.6 - true - - - - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - CS1998 - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\packages\AuthBot.3.1.2-alpha\lib\net40\AuthBot.dll - True - - - ..\packages\Autofac.4.1.1\lib\net45\Autofac.dll - True - - - ..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll - True - - - ..\packages\Microsoft.Bot.Builder.3.2.1\lib\net46\Microsoft.Bot.Builder.dll - True - - - ..\packages\Microsoft.Bot.Builder.3.2.1\lib\net46\Microsoft.Bot.Connector.dll - True - - - - ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.dll - True - - - ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.Platform.dll - True - - - ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.12.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll - True - - - ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.12.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll - True - - - ..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll - True - - - ..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll - True - - - ..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll - True - - - ..\packages\Microsoft.Rest.ClientRuntime.2.3.2\lib\net45\Microsoft.Rest.ClientRuntime.dll - True - - - ..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll - True - - - ..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll - True - - - ..\packages\Newtonsoft.Json.9.0.2-beta1\lib\net45\Newtonsoft.Json.dll - True - - - - - ..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll - True - - - ..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll - True - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - - - ..\packages\System.Runtime.Serialization.Primitives.4.1.1\lib\net46\System.Runtime.Serialization.Primitives.dll - True - - - ..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll - True - - - ..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - - - ..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - - - ..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll - True - - - - - - - - - - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll - True - - - - - - - - - - - - Designer - - - - - - - - - Global.asax - - - - - - - Web.config - - - Web.config - - - - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - true - - - - - - - - - True - - - - + + + + + Debug + AnyCPU + + + 2.0 + {DFDC62CE-993B-461E-99DB-F079F46105C5} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + SampleAADV2Bot + SampleAADV2Bot + v4.6 + true + + + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + CS1998 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + ..\packages\AuthBot.3.1.2-alpha\lib\net40\AuthBot.dll + True + + + ..\packages\Autofac.4.1.1\lib\net45\Autofac.dll + True + + + ..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll + True + + + ..\packages\Microsoft.Bot.Builder.3.2.1\lib\net46\Microsoft.Bot.Builder.dll + True + + + ..\packages\Microsoft.Bot.Builder.3.2.1\lib\net46\Microsoft.Bot.Connector.dll + True + + + + ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.dll + True + + + ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.Platform.dll + True + + + ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.12.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + True + + + ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.12.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + True + + + ..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll + True + + + ..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll + True + + + ..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll + True + + + ..\packages\Microsoft.Rest.ClientRuntime.2.3.2\lib\net45\Microsoft.Rest.ClientRuntime.dll + True + + + ..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll + True + + + ..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll + True + + + ..\packages\Newtonsoft.Json.9.0.2-beta1\lib\net45\Newtonsoft.Json.dll + True + + + + + ..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + True + + + ..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll + True + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + True + + + + + ..\packages\System.Runtime.Serialization.Primitives.4.1.1\lib\net46\System.Runtime.Serialization.Primitives.dll + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll + True + + + ..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + + + ..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + + + ..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll + True + + + + + + + + + + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll + True + + + + + + + + + + + + Designer + + + + + + + + + Global.asax + + + + + + + Web.config + + + Web.config + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + true + + + + + + + + + True + + + + + --> \ No newline at end of file diff --git a/SampleAADv2Bot/Web.config b/SampleAADv2Bot/Web.config index 61be353..87a94db 100644 --- a/SampleAADv2Bot/Web.config +++ b/SampleAADv2Bot/Web.config @@ -1,100 +1,100 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SampleAADv2Bot/packages.config b/SampleAADv2Bot/packages.config index 7953ae4..ec666a5 100644 --- a/SampleAADv2Bot/packages.config +++ b/SampleAADv2Bot/packages.config @@ -1,51 +1,51 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleVSOBot.md b/SampleVSOBot.md new file mode 100644 index 0000000..1181efd --- /dev/null +++ b/SampleVSOBot.md @@ -0,0 +1,44 @@ +#Using Authbot to authenticate in Visual Studio Online +Here are more details about VSO authentication with Authbot + +##Commands +The sample bot understands the following commands: + +Command | Description +--- | --- +logon or login | This will kick off the login process +logout | If will log you out +token | Will show your current VSO token. If the token expired, it will request a new one. +echo | Will just reply echo so you know the bot is alive. +projects | After you logged in, it will connect to Visual Studio Online (via REST Api) and query the List of projects at root level. + +##Bot in action + + +##Configuring web.config +```xml + + + + + + + + + + + + + + + +``` + +## VSO oAuth flow + + +##Relevant links +###https://app.vsaex.visualstudio.com/ +###https://app.vsaex.visualstudio.com/app/register +###[Authorize access to REST APIs with OAuth 2.0](https://www.visualstudio.com/docs/integrate/get-started/auth/oauth) +###[Sandbox for Visual Studio Rest API](https://apisandbox.msdn.microsoft.com/) diff --git a/SampleVSOBot/App_Start/WebApiConfig.cs b/SampleVSOBot/App_Start/WebApiConfig.cs new file mode 100644 index 0000000..45600fe --- /dev/null +++ b/SampleVSOBot/App_Start/WebApiConfig.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. +namespace SampleVSOBot +{ + using System.Web.Http; + using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; + + public static class WebApiConfig + { + public static void Register(HttpConfiguration config) + { + // Json settings + config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; + config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented; + JsonConvert.DefaultSettings = () => new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Formatting = Newtonsoft.Json.Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + }; + + // Web API configuration and services + + // Web API routes + config.MapHttpAttributeRoutes(); + + config.Routes.MapHttpRoute( + name: "DefaultApi", + routeTemplate: "api/{controller}/{id}", + defaults: new { id = RouteParameter.Optional }); + } + } +} + +//********************************************************* +// +//AuthBot, https://github.com/matvelloso/AuthBot +// +//Copyright (c) Microsoft Corporation +//All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// ""Software""), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: + + + + +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. + + + + +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//********************************************************* diff --git a/SampleVSOBot/Controllers/MessagesController.cs b/SampleVSOBot/Controllers/MessagesController.cs new file mode 100644 index 0000000..eda929b --- /dev/null +++ b/SampleVSOBot/Controllers/MessagesController.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. +namespace SampleVSOBot +{ + using System.Linq; + using System.Text.RegularExpressions; + using System.Threading.Tasks; + using System.Web.Http; + using Dialogs; + using Microsoft.Bot.Builder.Dialogs; + using Microsoft.Bot.Builder.FormFlow; + using Microsoft.Bot.Connector; + using System.Web.Http.Description; + using System.Net.Http; + + [BotAuthentication] + public class MessagesController : ApiController + { + /// + /// POST: api/Messages + /// Receive a message from a user and reply to it + /// + [BotAuthentication] + + public virtual async Task Post([FromBody]Activity activity) + { + if (activity != null && activity.GetActivityType() == ActivityTypes.Message) + { + await Conversation.SendAsync(activity, () => new ActionDialog()); + } + else + { + this.HandleSystemMessage(activity); + } + return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted); + } + + private Activity HandleSystemMessage(Activity message) + { + if (message.Type == ActivityTypes.Ping) + { + Activity reply = message.CreateReply(); + reply.Type = ActivityTypes.Ping; + return reply; + } + + return null; + } + } +} +//********************************************************* +// +//AuthBot, https://github.com/matvelloso/AuthBot +// +//Copyright (c) Microsoft Corporation +//All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// ""Software""), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: + + + + +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. + + + + +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//********************************************************* diff --git a/SampleVSOBot/Dialogs/ActionDialog.cs b/SampleVSOBot/Dialogs/ActionDialog.cs new file mode 100644 index 0000000..b09ca53 --- /dev/null +++ b/SampleVSOBot/Dialogs/ActionDialog.cs @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. +namespace SampleVSOBot.Dialogs +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using AuthBot; + using AuthBot.Dialogs; + using AuthBot.Models; + using Microsoft.Bot.Builder.Dialogs; + using Microsoft.Bot.Connector; + using System.Configuration; + using Helpers; + + [Serializable] + public class ActionDialog : IDialog + { + + public async Task StartAsync(IDialogContext context) + { + context.Wait(MessageReceivedAsync); + } + + + public async Task GetTokenInfo(IDialogContext context) + { + + AuthResult authResult; + if (context.UserData.TryGetValue(ContextConstants.AuthResultKey, out authResult)) + { + DateTime expiredDate = new DateTime(authResult.ExpiresOnUtcTicks); + String expireDateStr = expiredDate.ToString("d/M/yyyy HH:mm:ss"); + + var expiresin = TimeSpan.FromTicks(authResult.ExpiresOnUtcTicks - DateTime.UtcNow.Ticks).TotalMinutes; + if (expiresin < 0) + { + await context.PostAsync($"Your access token already expired on {expireDateStr}"); + } + else + { + await context.PostAsync($"Your access token expires in {expiresin} minutes ({expireDateStr})"); + await context.PostAsync($"Your access token is \"{authResult.AccessToken}\"."); + } + + } + else + { await context.PostAsync($"Please logon first"); } + + context.Wait(MessageReceivedAsync); + } + /// + /// For Testing Purposes / capability to force token expiration + /// + /// + /// + public async Task ForceTokenExpiration(IDialogContext context) + { + AuthResult authResult; + if (context.UserData.TryGetValue(ContextConstants.AuthResultKey, out authResult)) + { + authResult.ExpiresOnUtcTicks = 0; + context.StoreAuthResult(authResult); + await context.PostAsync($"Forced token to expire"); + } + context.Wait(MessageReceivedAsync); + } + + public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable item) + { + var message = await item; + + if (message.Text.ToLower() == "logon" || message.Text.ToLower() == "login") + { + //endpoint v1 + if (string.IsNullOrEmpty(await context.GetAccessToken(ConfigurationManager.AppSettings["ActiveDirectory.ResourceId"]))) + { + await context.Forward(new AzureAuthDialog(ConfigurationManager.AppSettings["ActiveDirectory.ResourceId"]), this.ResumeAfterAuth, message, CancellationToken.None); + } + else + { + await context.PostAsync("already logged in."); + context.Wait(MessageReceivedAsync); + } + } + else if (message.Text.ToLower() == "echo") + { + await context.PostAsync("echo"); + + context.Wait(this.MessageReceivedAsync); + } + else if (message.Text.ToLower() == "token") + { + await GetTokenInfo(context); + } + else if (message.Text.ToLower() == "expire") + { + await ForceTokenExpiration(context); + } + else if (message.Text.ToLower() == "logout") + { + await context.Logout(); + context.Wait(this.MessageReceivedAsync); + } + else if (message.Text.ToLower() == "projects") + { + await GetVSOProjectList(context); + } + else + { + await context.PostAsync("say what ? Please say something like logon, logout, token, echo or projects"); + context.Wait(MessageReceivedAsync); + } + } + + + public async Task GetVSOProjectList(IDialogContext context) + { + var accessToken = await context.GetAccessToken(ConfigurationManager.AppSettings["ActiveDirectory.ResourceId"]); + + try + { //todo: check if token has expired + if (string.IsNullOrEmpty(accessToken)) + { + await context.PostAsync("Please logon first"); + context.Wait(MessageReceivedAsync); + } + else + { + //String witems = await VSORestHelper.GetWorkItems(accessToken); + //String witems = await VSORestHelper.QueryWorkItems_Query(accessToken); + //String witems = await VSORestHelper.QueryWorkItems_Wiql(accessToken); + String witems = await VSORestHelper.ListProjects(accessToken); + await context.PostAsync(witems); + } + } + catch (Exception exc) + { await context.PostAsync($"Error geting work items. Error:\n {exc.Message}"); } + + context.Wait(MessageReceivedAsync); + } + private async Task ResumeAfterAuth(IDialogContext context, IAwaitable result) + { + var message = await result; + + await context.PostAsync(message); + context.Wait(MessageReceivedAsync); + } + } +} + + +//********************************************************* +// +//AuthBot, https://github.com/matvelloso/AuthBot +// +//Copyright (c) Microsoft Corporation +//All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// ""Software""), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: + + + + +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. + + + + +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//********************************************************* diff --git a/SampleVSOBot/Dialogs/DialogExtensions.cs b/SampleVSOBot/Dialogs/DialogExtensions.cs new file mode 100644 index 0000000..6c8bb52 --- /dev/null +++ b/SampleVSOBot/Dialogs/DialogExtensions.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. +namespace SampleVSOBot.Dialogs +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Autofac; + using Microsoft.Bot.Builder.Dialogs; + using Microsoft.Bot.Builder.Dialogs.Internals; + using Microsoft.Bot.Builder.Luis.Models; + using Microsoft.Bot.Connector; + + public static class DialogExtensions + { + public static void NotifyLongRunningOperation(this Task operation, IDialogContext context, Func handler) + { + operation.ContinueWith( + async (t, ctx) => + { + var messageText = handler(t.Result); + await NotifyUser((IDialogContext)ctx, messageText); + }, + context); + } + + public static void NotifyLongRunningOperation(this Task operation, IDialogContext context, Func handler) + { + operation.ContinueWith( + async (t, ctx) => + { + var messageText = handler(t.Result, (IDialogContext)ctx); + await NotifyUser((IDialogContext)ctx, messageText); + }, + context); + } + + public static string GetEntityOriginalText(this EntityRecommendation recommendation, string query) + { + if (recommendation.StartIndex.HasValue && recommendation.EndIndex.HasValue) + { + return query.Substring(recommendation.StartIndex.Value, recommendation.EndIndex.Value - recommendation.StartIndex.Value + 1); + } + + return null; + } + + public static async Task NotifyUser(this IDialogContext context, string messageText) + { + if (!string.IsNullOrEmpty(messageText)) + { + await context.PostAsync(messageText); + } + } + } +} + +//********************************************************* +// +//AuthBot, https://github.com/matvelloso/AuthBot +// +//Copyright (c) Microsoft Corporation +//All rights reserved. +// +// MIT License: +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// ""Software""), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: + + + + +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. + + + + +// THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +//********************************************************* diff --git a/SampleVSOBot/Global.asax b/SampleVSOBot/Global.asax new file mode 100644 index 0000000..75be4e8 --- /dev/null +++ b/SampleVSOBot/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="SampleVSOBot.WebApiApplication" Language="C#" %> diff --git a/SampleVSOBot/Global.asax.cs b/SampleVSOBot/Global.asax.cs new file mode 100644 index 0000000..3751e03 --- /dev/null +++ b/SampleVSOBot/Global.asax.cs @@ -0,0 +1,23 @@ +using System; +using System.Configuration; +using System.Web.Http; + +namespace SampleVSOBot +{ + public class WebApiApplication : System.Web.HttpApplication + { + protected void Application_Start() + { + GlobalConfiguration.Configure(WebApiConfig.Register); + + AuthBot.Models.AuthSettings.Mode = ConfigurationManager.AppSettings["ActiveDirectory.Mode"]; + AuthBot.Models.AuthSettings.EndpointUrl = ConfigurationManager.AppSettings["ActiveDirectory.EndpointUrl"]; + AuthBot.Models.AuthSettings.Tenant = ConfigurationManager.AppSettings["ActiveDirectory.Tenant"]; + AuthBot.Models.AuthSettings.RedirectUrl = ConfigurationManager.AppSettings["ActiveDirectory.RedirectUrl"]; + AuthBot.Models.AuthSettings.ClientId = ConfigurationManager.AppSettings["ActiveDirectory.ClientId"]; + AuthBot.Models.AuthSettings.ClientSecret = ConfigurationManager.AppSettings["ActiveDirectory.ClientSecret"]; + AuthBot.Models.AuthSettings.Scopes = ConfigurationManager.AppSettings["ActiveDirectory.Scopes"].Split(','); + + } + } +} diff --git a/SampleVSOBot/Helpers/VSORestHelper.cs b/SampleVSOBot/Helpers/VSORestHelper.cs new file mode 100644 index 0000000..a7d7698 --- /dev/null +++ b/SampleVSOBot/Helpers/VSORestHelper.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.TeamFoundation.Core.WebApi; +using Microsoft.VisualStudio.Services.Common; +using System.Net.Http; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi; +using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; + +namespace SampleVSOBot.Helpers +{ + public static class VSORestHelper + { + public static async Task ListProjects(string token) + { + StringBuilder retStr = new StringBuilder(); + + retStr.AppendLine("Fetching project list\n"); + Uri uri = new Uri("https://" + AuthBot.Models.AuthSettings.Tenant + ".visualstudio.com/"); + VssBasicCredential credentials = new VssBasicCredential("", token); + + using (ProjectHttpClient projectHttpClient = new ProjectHttpClient(uri, credentials)) + { + + IEnumerable projects = projectHttpClient.GetProjects().Result; + retStr.AppendLine(String.Format("Found {0} Projects:\n", projects.Count())); + foreach (TeamProjectReference project in projects) + { + retStr.AppendLine(String.Format("{0}\n", project.Name)); + } + + } + return retStr.ToString(); + } + + #region Other sample ways to query TFS + + public static async Task GetWorkItems(string token) + { + using (var client = new HttpClient()) + { + var apiVersion = "1.0"; + StringBuilder strResult = new StringBuilder(); + + var query = "Select [System.Id] From WorkItems Where [System.WorkItemType] = 'Bug' order by [System.CreatedDate] desc"; + + client.DefaultRequestHeaders.Accept.Clear(); + + //var url = "https://" + AuthBot.Models.AuthSettings.Tenant + ".visualstudio.com/defaultcollection/_apis/wit/"; + //https://www.visualstudio.com/en-us/docs/integrate/get-started/rest/basics + //VERB https://{account}.VisualStudio.com/DefaultCollection/_apis[/{area}]/{resource}?api-version={version} + + var url = "https://" + AuthBot.Models.AuthSettings.Tenant + ".visualstudio.com/DefaultCollection/_apis/wit/"; + //var url = "https://" + AuthBot.Models.AuthSettings.Tenant + ".visualstudio.com/DefaultCollection/TED%20Consumer/_apis/wit/wiql"; + + // Execute a query that returns work item IDs matching the specified criteria + using (var request = new HttpRequestMessage(HttpMethod.Post, url)) + { + request.Headers.Add("Authorization", "Bearer " + token); + request.Headers.Add("Accept", "application/json;api-version=" + apiVersion); + + Dictionary body = new Dictionary + { + { + "query", query + } + }; + + request.Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); + + using (var response = await client.SendAsync(request)) + { + var content = await response.Content.ReadAsStringAsync(); + var workItems = JObject.Parse(content)["workItems"] as JArray; + + string[] ids = workItems.Select(w => (w["id"] + "")).Take(10).ToArray(); + string idsString = String.Join(",", ids); + + // Get details for the last 10 + using (var detailsRequest = new HttpRequestMessage(HttpMethod.Get, url + "workitems?ids=" + idsString + "&fields=System.Id,System.Title")) + { + detailsRequest.Headers.Add("Authorization", "Bearer " + token); + detailsRequest.Headers.Add("Accept", "application/json;api-version=" + apiVersion); + + using (var detailsResponse = await client.SendAsync(detailsRequest)) + { + var detailsContent = await detailsResponse.Content.ReadAsStringAsync(); + var detailsWorkItems = JObject.Parse(detailsContent)["value"] as JArray; + + foreach (dynamic workItem in detailsWorkItems) + { + strResult.AppendFormat("* Work item: {0} ({1})\n", + workItem.fields["System.Id"], + workItem.fields["System.Title"]); + } + } + } + return strResult.ToString(); + } + } + } + } + + + public static async Task QueryWorkItems_Query(string token) + { + string retStr="Nothing to return"; + string _personalAccessToken = token; + Uri uri = new Uri("https://" + AuthBot.Models.AuthSettings.Tenant + ".visualstudio.com/"); + VssBasicCredential _credentials = new VssBasicCredential("", _personalAccessToken); + + string project = "TED Consumer"; + string query = "Shared Queries/FY17/All Activities"; + + using (WorkItemTrackingHttpClient workItemTrackingHttpClient = new WorkItemTrackingHttpClient(uri, _credentials)) + { + QueryHierarchyItem queryItem; + + try + { + //get the query object based on the query name and project + queryItem = workItemTrackingHttpClient.GetQueryAsync(project, query).Result; + } + catch (Exception ex) + { + return ex.InnerException.Message; + } + + //now we have the query id, so lets execute the query and get the results + WorkItemQueryResult workItemQueryResult = workItemTrackingHttpClient.QueryByIdAsync(queryItem.Id).Result; + + //some error handling + if (workItemQueryResult != null) + { + //need to get the list of our work item id's and put them into an array + List list = new List(); + foreach (var item in workItemQueryResult.WorkItems) + { + list.Add(item.Id); + } + int[] arr = list.ToArray(); + + //build a list of the fields we want to see + string[] fields = new string[3]; + fields[0] = "System.Id"; + fields[1] = "System.Title"; + fields[2] = "System.State"; + + //var workItems = workItemTrackingHttpClient.GetWorkItemsAsync(arr, fields, workItemQueryResult.AsOf).Result; + retStr= String.Format("Found {0} items", arr.Length); + } + } + return retStr; + } + + + public static async Task QueryWorkItems_Wiql(string token) + { + string _personalAccessToken = token; + Uri uri = new Uri("https://" + AuthBot.Models.AuthSettings.Tenant + ".visualstudio.com/"); + VssBasicCredential _credentials = new VssBasicCredential("", _personalAccessToken); + + //needed to scope our query to the project + string project = "TED Consumer"; + + //var query = "Select [System.Id] From WorkItems Where [System.WorkItemType] = 'Bug' order by [System.CreatedDate] desc"; + //create a wiql object and build our query + Wiql wiql = new Wiql() + { + Query = "Select [State], [Title] " + + "From WorkItems " + + "Where [Work Item Type] = 'Bug' " + + "And [System.TeamProject] = '" + project + "' " + + "And [System.State] = 'New' " + + "Order By [State] Asc, [Changed Date] Desc" + }; + + //create instance of work item tracking http client + using (WorkItemTrackingHttpClient workItemTrackingHttpClient = new WorkItemTrackingHttpClient(uri, _credentials)) + { + //execute the query to get the list of work items in teh results + WorkItemQueryResult workItemQueryResult = workItemTrackingHttpClient.QueryByWiqlAsync(wiql).Result; + + //some error handling + if (workItemQueryResult != null) + { + //need to get the list of our work item id's and put them into an array + List list = new List(); + foreach (var item in workItemQueryResult.WorkItems) + { + list.Add(item.Id); + } + int[] arr = list.ToArray(); + + //build a list of the fields we want to see + string[] fields = new string[3]; + fields[0] = "System.Id"; + fields[1] = "System.Title"; + fields[2] = "System.State"; + + var workItems = workItemTrackingHttpClient.GetWorkItemsAsync(arr, fields, workItemQueryResult.AsOf).Result; + } + } + return "done"; + } + + #endregion + } + +} \ No newline at end of file diff --git a/SampleVSOBot/Properties/AssemblyInfo.cs b/SampleVSOBot/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b2cb87d --- /dev/null +++ b/SampleVSOBot/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft.Bot.Sample.EchoBot")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.Bot.Sample.EchoBot")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a8ba1066-5695-4d71-abb4-65e5a5e0c3d4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SampleVSOBot/SampleVSOBot.csproj b/SampleVSOBot/SampleVSOBot.csproj new file mode 100644 index 0000000..1d90482 --- /dev/null +++ b/SampleVSOBot/SampleVSOBot.csproj @@ -0,0 +1,290 @@ + + + + + + Debug + AnyCPU + + + 2.0 + {4EE65876-C3A8-4F9D-973A-42729E205F94} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + SampleVSOBot + SampleVSOBot + v4.6 + true + + + + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + CS1998 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + False + ..\packages\Autofac.4.1.1\lib\net45\Autofac.dll + + + ..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll + True + + + ..\packages\Microsoft.Bot.Builder.3.2.1\lib\net46\Microsoft.Bot.Builder.dll + True + + + ..\packages\Microsoft.Bot.Builder.3.2.1\lib\net46\Microsoft.Bot.Connector.dll + True + + + + ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.dll + True + + + ..\packages\Microsoft.Identity.Client.1.0.304142221-alpha\lib\net45\Microsoft.Identity.Client.Platform.dll + True + + + ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + True + + + ..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + True + + + ..\packages\Microsoft.IdentityModel.Logging.1.0.0\lib\net451\Microsoft.IdentityModel.Logging.dll + True + + + ..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll + + + ..\packages\Microsoft.IdentityModel.Tokens.5.0.0\lib\net451\Microsoft.IdentityModel.Tokens.dll + True + + + False + + + ..\packages\Microsoft.Rest.ClientRuntime.2.3.2\lib\net45\Microsoft.Rest.ClientRuntime.dll + True + + + ..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Build2.WebApi.dll + True + + + ..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Chat.WebApi.dll + True + + + ..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Common.dll + True + + + ..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Core.WebApi.dll + True + + + ..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Policy.WebApi.dll + True + + + ..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.SourceControl.WebApi.dll + True + + + ..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Test.WebApi.dll + True + + + ..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.TestManagement.WebApi.dll + True + + + ..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.Work.WebApi.dll + True + + + ..\packages\Microsoft.TeamFoundationServer.Client.14.102.0\lib\net45\Microsoft.TeamFoundation.WorkItemTracking.WebApi.dll + True + + + ..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.Common.dll + True + + + ..\packages\Microsoft.VisualStudio.Services.Client.14.102.0\lib\net45\Microsoft.VisualStudio.Services.WebApi.dll + True + + + ..\packages\Microsoft.Win32.Primitives.4.0.1\lib\net46\Microsoft.Win32.Primitives.dll + True + + + ..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.1\lib\net40\Microsoft.WindowsAzure.Configuration.dll + True + + + ..\packages\Newtonsoft.Json.9.0.2-beta1\lib\net45\Newtonsoft.Json.dll + True + + + + + ..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\net46\System.Diagnostics.DiagnosticSource.dll + True + + + ..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll + + + + False + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + True + + + + + ..\packages\System.Runtime.Serialization.Primitives.4.1.1\lib\net46\System.Runtime.Serialization.Primitives.dll + True + + + ..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net46\System.Security.Cryptography.Algorithms.dll + True + + + ..\packages\System.Security.Cryptography.Encoding.4.0.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + + + ..\packages\System.Security.Cryptography.Primitives.4.0.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + + + ..\packages\System.Security.Cryptography.X509Certificates.4.1.0\lib\net46\System.Security.Cryptography.X509Certificates.dll + True + + + + + + + + + + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + True + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll + True + + + + + + + + + + + + Designer + + + + + + + + + Global.asax + + + + + + + Designer + + + Web.config + + + Web.config + + + + + + + + {b8ad59d3-c36d-4e18-b504-06871001bc8d} + AuthBot + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + true + + + + + + + + + True + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/SampleVSOBot/Web.Debug.config b/SampleVSOBot/Web.Debug.config new file mode 100644 index 0000000..2e302f9 --- /dev/null +++ b/SampleVSOBot/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/SampleVSOBot/Web.Release.config b/SampleVSOBot/Web.Release.config new file mode 100644 index 0000000..5a930c5 --- /dev/null +++ b/SampleVSOBot/Web.Release.config @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleVSOBot/Web.config b/SampleVSOBot/Web.config new file mode 100644 index 0000000..a397c50 --- /dev/null +++ b/SampleVSOBot/Web.config @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SampleVSOBot/default.htm b/SampleVSOBot/default.htm new file mode 100644 index 0000000..74157bc --- /dev/null +++ b/SampleVSOBot/default.htm @@ -0,0 +1,13 @@ + + + + + + + +

Sample VSO Bot (1.1a)

+ + + + + diff --git a/SampleVSOBot/packages.config b/SampleVSOBot/packages.config new file mode 100644 index 0000000..a91e831 --- /dev/null +++ b/SampleVSOBot/packages.config @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VSOBotSampeInAction.PNG b/VSOBotSampeInAction.PNG new file mode 100644 index 0000000..f88f217 Binary files /dev/null and b/VSOBotSampeInAction.PNG differ diff --git a/VSOFlow.PNG b/VSOFlow.PNG new file mode 100644 index 0000000..50f0dca Binary files /dev/null and b/VSOFlow.PNG differ