diff options
Diffstat (limited to 'src')
12 files changed, 77 insertions, 129 deletions
diff --git a/src/.gitignore b/src/.gitignore index 420748a..a5031e1 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -9,3 +9,4 @@ _ReSharper.* bin obj Bin +packages
\ No newline at end of file diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClaims.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClaims.cs index eda649c..deb396f 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClaims.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClaims.cs @@ -4,8 +4,7 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.AspNet.Clients -{ +namespace DotNetOpenAuth.AspNet.Clients { using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; @@ -20,8 +19,7 @@ namespace DotNetOpenAuth.AspNet.Clients [DataContract] [EditorBrowsable(EditorBrowsableState.Never)] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "AzureAD", Justification = "Brand name")] - public class AzureADClaims - { + public class AzureADClaims { #region Public Properties /// <summary> diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClient.cs index f596ba0..fa8f707 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClient.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClient.cs @@ -105,8 +105,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// The app secret. /// </param> public AzureADClient(string appId, string appSecret) - : this(appId, appSecret, GraphResource) - { + : this(appId, appSecret, GraphResource) { } /// <summary> @@ -122,8 +121,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// The resource of oauth request. /// </param> public AzureADClient(string appId, string appSecret, string resource) - : base("azuread") - { + : base("azuread") { Requires.NotNullOrEmpty(appId, "appId"); Requires.NotNullOrEmpty(appSecret, "appSecret"); Requires.NotNullOrEmpty(resource, "resource"); @@ -161,21 +159,17 @@ namespace DotNetOpenAuth.AspNet.Clients { /// The access token. /// </param> /// <returns>A dictionary of profile data.</returns> - protected override IDictionary<string, string> GetUserData(string accessToken) - { + protected override IDictionary<string, string> GetUserData(string accessToken) { IDictionary<string, string> userData = new Dictionary<string, string>(); - try - { + try { AzureADGraph graphData; WebRequest request = WebRequest.Create( GraphEndpoint + this.tenantid + "/users/" + this.userid + "?api-version=2013-04-05"); request.Headers = new WebHeaderCollection(); request.Headers.Add("authorization", accessToken); - using (var response = request.GetResponse()) - { - using (var responseStream = response.GetResponseStream()) - { + using (var response = request.GetResponse()) { + using (var responseStream = response.GetResponseStream()) { graphData = JsonHelper.Deserialize<AzureADGraph>(responseStream); } } @@ -186,9 +180,7 @@ namespace DotNetOpenAuth.AspNet.Clients { userData.AddItemIfNotEmpty("name", graphData.DisplayName); return userData; - } - catch (Exception e) - { + } catch (Exception e) { System.Diagnostics.Debug.WriteLine(e.ToStringDescriptive()); return userData; } @@ -206,10 +198,8 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <returns> /// The access token. /// </returns> - protected override string QueryAccessToken(Uri returnUrl, string authorizationCode) - { - try - { + protected override string QueryAccessToken(Uri returnUrl, string authorizationCode) { + try { var entity = MessagingUtilities.CreateQueryString( new Dictionary<string, string> { @@ -226,25 +216,20 @@ namespace DotNetOpenAuth.AspNet.Clients { tokenRequest.ContentLength = entity.Length; tokenRequest.Method = "POST"; - using (Stream requestStream = tokenRequest.GetRequestStream()) - { + using (Stream requestStream = tokenRequest.GetRequestStream()) { var writer = new StreamWriter(requestStream); writer.Write(entity); writer.Flush(); } HttpWebResponse tokenResponse = (HttpWebResponse)tokenRequest.GetResponse(); - if (tokenResponse.StatusCode == HttpStatusCode.OK) - { - using (Stream responseStream = tokenResponse.GetResponseStream()) - { + if (tokenResponse.StatusCode == HttpStatusCode.OK) { + using (Stream responseStream = tokenResponse.GetResponseStream()) { var tokenData = JsonHelper.Deserialize<OAuth2AccessTokenData>(responseStream); - if (tokenData != null) - { + if (tokenData != null) { AzureADClaims claimsAD; claimsAD = this.ParseAccessToken(tokenData.AccessToken, true); - if (claimsAD != null) - { + if (claimsAD != null) { this.tenantid = claimsAD.Tid; this.userid = claimsAD.Oid; return tokenData.AccessToken; @@ -255,9 +240,7 @@ namespace DotNetOpenAuth.AspNet.Clients { } return null; - } - catch (Exception e) - { + } catch (Exception e) { System.Diagnostics.Debug.WriteLine(e.ToStringDescriptive()); return null; } @@ -272,8 +255,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <returns> /// Decoded string as string using UTF8 encoding. /// </returns> - private static string Base64URLdecode(string str) - { + private static string Base64URLdecode(string str) { System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding(); return encoder.GetString(Base64URLdecodebyte(str)); } @@ -287,8 +269,7 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <returns> /// Decoded string as bytes. /// </returns> - private static byte[] Base64URLdecodebyte(string str) - { + private static byte[] Base64URLdecodebyte(string str) { // First replace chars and then pad per spec str = str.Replace('-', '+').Replace('_', '/'); str = str.PadRight(str.Length + ((4 - (str.Length % 4)) % 4), '='); @@ -310,53 +291,42 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <returns> /// True if same, false otherwise. /// </returns> - private static bool ValidateSig(byte[] uval, byte[] sval, byte[] certthumb) - { - try - { + private static bool ValidateSig(byte[] uval, byte[] sval, byte[] certthumb) { + try { bool ret = false; X509Certificate2[] certx509 = GetEncodingCert(); string certthumbhex = string.Empty; // Get the hexadecimail representation of the certthumbprint - for (int i = 0; i < certthumb.Length; i++) - { + for (int i = 0; i < certthumb.Length; i++) { certthumbhex += certthumb[i].ToString("X2"); } - for (int c = 0; c < certx509.Length; c++) - { + for (int c = 0; c < certx509.Length; c++) { // Skip any cert that does not have the same thumbprint as token - if (certx509[c].Thumbprint.ToLower() != certthumbhex.ToLower()) - { + if (certx509[c].Thumbprint.ToLower() != certthumbhex.ToLower()) { continue; } X509SecurityToken tok = new X509SecurityToken(certx509[c]); - if (tok == null) - { + if (tok == null) { return false; } - for (int i = 0; i < tok.SecurityKeys.Count; i++) - { + for (int i = 0; i < tok.SecurityKeys.Count; i++) { X509AsymmetricSecurityKey key = tok.SecurityKeys[i] as X509AsymmetricSecurityKey; RSACryptoServiceProvider rsa = key.GetAsymmetricAlgorithm(SecurityAlgorithms.RsaSha256Signature, false) as RSACryptoServiceProvider; - if (rsa == null) - { + if (rsa == null) { continue; } ret = rsa.VerifyData(uval, hash, sval); - if (ret == true) - { + if (ret == true) { return ret; } } } return ret; - } - catch (CryptographicException e) - { + } catch (CryptographicException e) { Console.WriteLine(e.ToStringDescriptive()); return false; } @@ -368,44 +338,34 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <returns> /// The encoding certificate. /// </returns> - private static X509Certificate2[] GetEncodingCert() - { - if (encodingcert != null) - { + private static X509Certificate2[] GetEncodingCert() { + if (encodingcert != null) { return encodingcert; } - try - { + try { // Lock for exclusive access - lock (typeof(AzureADClient)) - { + lock (typeof(AzureADClient)) { XmlDocument doc = new XmlDocument(); WebRequest request = WebRequest.Create(MetaDataEndpoint); - using (WebResponse response = request.GetResponse()) - { - using (Stream responseStream = response.GetResponseStream()) - { + using (WebResponse response = request.GetResponse()) { + using (Stream responseStream = response.GetResponseStream()) { doc.Load(responseStream); XmlNodeList list = doc.GetElementsByTagName("X509Certificate"); encodingcert = new X509Certificate2[list.Count]; - for (int i = 0; i < list.Count; i++) - { + for (int i = 0; i < list.Count; i++) { byte[] todecode_byte = Convert.FromBase64String(list[i].InnerText); encodingcert[i] = new X509Certificate2(todecode_byte); } - if (hash == null) - { + if (hash == null) { hash = SHA256.Create(); } } } } return encodingcert; - } - catch (Exception e) - { + } catch (Exception e) { System.Diagnostics.Debug.WriteLine(e.ToStringDescriptive()); return null; } @@ -423,10 +383,8 @@ namespace DotNetOpenAuth.AspNet.Clients { /// <returns> /// The claims as an object and null in case of failure. /// </returns> - private AzureADClaims ParseAccessToken(string token, bool validate) - { - try - { + private AzureADClaims ParseAccessToken(string token, bool validate) { + try { // This is the encoded JWT token split into the 3 parts string[] strparts = token.Split('.'); @@ -434,8 +392,7 @@ namespace DotNetOpenAuth.AspNet.Clients { string jwtHeader, jwtClaims; string jwtb64Header, jwtb64Claims, jwtb64Sig; byte[] jwtSig; - if (strparts.Length != 3) - { + if (strparts.Length != 3) { return null; } jwtb64Header = strparts[0]; @@ -450,34 +407,28 @@ namespace DotNetOpenAuth.AspNet.Clients { AzureADClaims claimsAD = s1.Deserialize<AzureADClaims>(jwtClaims); AzureADHeader headerAD = s1.Deserialize<AzureADHeader>(jwtHeader); - if (validate) - { + if (validate) { // Check to see if the token is valid // Check if its JWT and RSA encoded - if (headerAD.Typ.ToUpper() != "JWT") - { + if (headerAD.Typ.ToUpper() != "JWT") { return null; } // Check if its JWT and RSA encoded - if (headerAD.Alg.ToUpper() != "RS256") - { + if (headerAD.Alg.ToUpper() != "RS256") { return null; } - if (string.IsNullOrEmpty(headerAD.X5t)) - { + if (string.IsNullOrEmpty(headerAD.X5t)) { return null; } // Check audience to be graph - if (claimsAD.Aud.ToLower().ToLower() != GraphResource.ToLower()) - { + if (claimsAD.Aud.ToLower().ToLower() != GraphResource.ToLower()) { return null; } // Check issuer to be sts - if (claimsAD.Iss.ToLower().IndexOf(STSName.ToLower()) != 0) - { + if (claimsAD.Iss.ToLower().IndexOf(STSName.ToLower()) != 0) { return null; } @@ -486,22 +437,18 @@ namespace DotNetOpenAuth.AspNet.Clients { double secsnow = span.TotalSeconds; double nbfsecs = Convert.ToDouble(claimsAD.Nbf); double expsecs = Convert.ToDouble(claimsAD.Exp); - if ((nbfsecs - 100 > secsnow) || (secsnow > expsecs + 100)) - { + if ((nbfsecs - 100 > secsnow) || (secsnow > expsecs + 100)) { return null; } // Validate the signature of the token string tokUnsigned = jwtb64Header + "." + jwtb64Claims; - if (!ValidateSig(Encoding.UTF8.GetBytes(tokUnsigned), jwtSig, Base64URLdecodebyte(headerAD.X5t))) - { + if (!ValidateSig(Encoding.UTF8.GetBytes(tokUnsigned), jwtSig, Base64URLdecodebyte(headerAD.X5t))) { return null; } } return claimsAD; - } - catch (Exception e) - { + } catch (Exception e) { System.Diagnostics.Debug.WriteLine(e.ToStringDescriptive()); return null; } diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADHeader.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADHeader.cs index 7632900..042eccb 100644 --- a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADHeader.cs +++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADHeader.cs @@ -4,8 +4,7 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.AspNet.Clients -{ +namespace DotNetOpenAuth.AspNet.Clients { using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; @@ -21,8 +20,7 @@ namespace DotNetOpenAuth.AspNet.Clients [EditorBrowsable(EditorBrowsableState.Never)] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "AzureAD", Justification = "Brand name")] - public class AzureADHeader - { + public class AzureADHeader { #region Public Properties /// <summary> diff --git a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs index eff035a..ab4aadf 100644 --- a/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth.Core/Messaging/MessagingUtilities.cs @@ -696,11 +696,14 @@ namespace DotNetOpenAuth.Messaging { /// Gets a NON-cryptographically strong random string of base64 characters. /// </summary> /// <param name="binaryLength">The length of the byte sequence to generate.</param> - /// <returns>A base64 encoding of the generated random data, - /// whose length in characters will likely be greater than <paramref name="binaryLength"/>.</returns> - internal static string GetNonCryptoRandomDataAsBase64(int binaryLength) { + /// <param name="useWeb64">A value indicating whether web64 encoding is used to avoid the need to escape characters.</param> + /// <returns> + /// A base64 encoding of the generated random data, + /// whose length in characters will likely be greater than <paramref name="binaryLength" />. + /// </returns> + internal static string GetNonCryptoRandomDataAsBase64(int binaryLength, bool useWeb64 = false) { byte[] uniq_bytes = GetNonCryptoRandomData(binaryLength); - string uniq = Convert.ToBase64String(uniq_bytes); + string uniq = useWeb64 ? ConvertToBase64WebSafeString(uniq_bytes) : Convert.ToBase64String(uniq_bytes); return uniq; } diff --git a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs index e1e9d53..3b9ab41 100644 --- a/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs +++ b/src/DotNetOpenAuth.Core/Messaging/OutgoingWebResponse.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.Messaging { using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; + using System.Globalization; using System.IO; using System.Net; using System.Net.Mime; @@ -318,6 +319,7 @@ namespace DotNetOpenAuth.Messaging { writer.Write(body); writer.Flush(); this.ResponseStream.Seek(0, SeekOrigin.Begin); + this.Headers[HttpResponseHeader.ContentLength] = this.ResponseStream.Length.ToString(CultureInfo.InvariantCulture); } /// <summary> diff --git a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs index adca925..65d4827 100644 --- a/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs +++ b/src/DotNetOpenAuth.Core/Messaging/StandardWebRequestHandler.cs @@ -167,7 +167,7 @@ namespace DotNetOpenAuth.Messaging { } } else { Logger.Http.ErrorFormat( - "{0} connecting to {0}", + "{0} connecting to {1}", ex.Status, request.RequestUri); } diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/BearerTokenHttpMessageHandler.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/BearerTokenHttpMessageHandler.cs index 6b2e937..2208700 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/BearerTokenHttpMessageHandler.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/BearerTokenHttpMessageHandler.cs @@ -75,7 +75,7 @@ namespace DotNetOpenAuth.OAuth2 { protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { string bearerToken = this.BearerToken; if (bearerToken == null) { - ErrorUtilities.VerifyProtocol(!this.Authorization.AccessTokenExpirationUtc.HasValue || this.Authorization.AccessTokenExpirationUtc < DateTime.UtcNow || this.Authorization.RefreshToken != null, ClientStrings.AuthorizationExpired); + ErrorUtilities.VerifyProtocol(!this.Authorization.AccessTokenExpirationUtc.HasValue || this.Authorization.AccessTokenExpirationUtc >= DateTime.UtcNow || this.Authorization.RefreshToken != null, ClientStrings.AuthorizationExpired); if (this.Authorization.AccessTokenExpirationUtc.HasValue && this.Authorization.AccessTokenExpirationUtc.Value < DateTime.UtcNow) { ErrorUtilities.VerifyProtocol(this.Authorization.RefreshToken != null, ClientStrings.AccessTokenRefreshFailed); diff --git a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs index 879e4e3..277bed4 100644 --- a/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs +++ b/src/DotNetOpenAuth.OAuth2.Client/OAuth2/WebServerClient.cs @@ -113,7 +113,7 @@ namespace DotNetOpenAuth.OAuth2 { if (this.AuthorizationTracker == null) { var context = this.Channel.GetHttpContext(); - string xsrfKey = MessagingUtilities.GetNonCryptoRandomDataAsBase64(16); + string xsrfKey = MessagingUtilities.GetNonCryptoRandomDataAsBase64(16, useWeb64: true); cookie = new HttpCookie(XsrfCookieName, xsrfKey) { HttpOnly = true, Secure = FormsAuthentication.RequireSSL, diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs index af60596..a313519 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs @@ -192,7 +192,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { } /// <summary> - /// Gets or sets a combination o the language and country of the user. + /// Gets or sets a combination of the language and country of the user. /// </summary> [XmlIgnore] public CultureInfo Culture { @@ -203,7 +203,16 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { if (!string.IsNullOrEmpty(this.Country)) { cultureString += "-" + this.Country; } - this.culture = CultureInfo.GetCultureInfo(cultureString); + + // language-country may not always form a recongized valid culture. + // For instance, a Google OpenID Provider can return a random combination + // of language and country based on user settings. + try { + this.culture = CultureInfo.GetCultureInfo(cultureString); + } catch (ArgumentException) { // CultureNotFoundException derives from this, and .NET 3.5 throws the base type + // Fallback to just reporting a culture based on language. + this.culture = CultureInfo.GetCultureInfo(this.Language); + } } return this.culture; diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln index babe701..8a44fd7 100644 --- a/src/DotNetOpenAuth.sln +++ b/src/DotNetOpenAuth.sln @@ -219,8 +219,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth.Common EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.OAuth2.ClientAuthorization", "DotNetOpenAuth.OAuth2.ClientAuthorization\DotNetOpenAuth.OAuth2.ClientAuthorization.csproj", "{CCF3728A-B3D7-404A-9BC6-75197135F2D7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestAzureAD", "..\samples\TestAzureAD\TestAzureAD.csproj", "{C62A052B-7914-4511-942A-A3F4609AC77A}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CodeAnalysis|Any CPU = CodeAnalysis|Any CPU @@ -505,12 +503,6 @@ Global {CCF3728A-B3D7-404A-9BC6-75197135F2D7}.Debug|Any CPU.Build.0 = Debug|Any CPU {CCF3728A-B3D7-404A-9BC6-75197135F2D7}.Release|Any CPU.ActiveCfg = Release|Any CPU {CCF3728A-B3D7-404A-9BC6-75197135F2D7}.Release|Any CPU.Build.0 = Release|Any CPU - {C62A052B-7914-4511-942A-A3F4609AC77A}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {C62A052B-7914-4511-942A-A3F4609AC77A}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU - {C62A052B-7914-4511-942A-A3F4609AC77A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C62A052B-7914-4511-942A-A3F4609AC77A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C62A052B-7914-4511-942A-A3F4609AC77A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C62A052B-7914-4511-942A-A3F4609AC77A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -522,7 +514,6 @@ Global {8A5CEDB9-7F8A-4BE2-A1B9-97130F453277} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} {2DA24D4F-6918-43CF-973C-BC9D818F8E90} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} {AA78D112-D889-414B-A7D4-467B34C7B663} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} - {C62A052B-7914-4511-942A-A3F4609AC77A} = {B4C6F647-C046-4B54-BE12-7701C4119EE7} {2A59DE0A-B76A-4B42-9A33-04D34548353D} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} {AEA29D4D-396F-47F6-BC81-B58D4B855245} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} {07B193F1-68AD-4E9C-98AF-BEFB5E9403CB} = {034D5B5B-7D00-4A9D-8AFE-4A476E0575B1} diff --git a/src/version.txt b/src/version.txt index 626c1f6..f77856a 100644 --- a/src/version.txt +++ b/src/version.txt @@ -1,2 +1 @@ -4.3.0 --ctp1 +4.3.1 |