diff --git a/dotnet/src/dotnetframework/GxClasses/Domain/GxCollections.cs b/dotnet/src/dotnetframework/GxClasses/Domain/GxCollections.cs index ebb39a744..d132245e8 100644 --- a/dotnet/src/dotnetframework/GxClasses/Domain/GxCollections.cs +++ b/dotnet/src/dotnetframework/GxClasses/Domain/GxCollections.cs @@ -1631,12 +1631,32 @@ public void AddObjectProperty(string name, object prop, bool includeState, bool JsonObj.Put(name, prop); } } + else if (prop is decimal) + { + JsonObj.Put(name, RemoveInternalTrailingZeroes((decimal)prop)); + } else { JsonObj.Put(name, prop); } } } + static decimal RemoveInternalTrailingZeroes(decimal dec) + { + if (GetDecimalScale(dec) > 0) + { + string strdec = dec.ToString(CultureInfo.InvariantCulture); + return decimal.Parse(strdec.Contains(".") ? strdec.TrimEnd('0').TrimEnd('.') : strdec, CultureInfo.InvariantCulture); + } + else + return dec; + + } + static int GetDecimalScale(decimal value) + { + int[] bits = decimal.GetBits(value); + return (bits[3] >> 16) & 0xFF; + } public Object GetJSONObject(bool includeState, bool includeNoInitialized) { JsonObj.Clear(); @@ -3185,4 +3205,4 @@ public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOp } } #endif -} \ No newline at end of file +} diff --git a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs index 9505fde13..0744065dc 100644 --- a/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs +++ b/dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs @@ -1860,14 +1860,17 @@ protected string GetObjectAccessWebToken(String cmpCtx) return GetSecureSignedToken(cmpCtx, string.Empty, this.context); } - protected string GetSecureSignedToken(String cmpCtx, object Value, IGxContext context) + protected string GetSecureSignedToken(String cmpCtx, object value, IGxContext context) { - return GetSecureSignedToken(cmpCtx, Serialize(Value), context); + if (value is IGxJSONSerializable) + return GetSecureSignedHashedToken(cmpCtx, SecureTokenHelper.GetTokenValue(value as IGxJSONSerializable), context); + else + return GetSecureSignedToken(cmpCtx, Serialize(value), context); } protected string GetSecureSignedToken(String cmpCtx, GxUserType Value, IGxContext context) { - return GetSecureSignedToken(cmpCtx, Serialize(Value), context); + return GetSecureSignedHashedToken(cmpCtx, SecureTokenHelper.GetTokenValue(Value), context); } @@ -1875,6 +1878,11 @@ protected string GetSecureSignedToken(string cmpCtx, string value, IGxContext co { return WebSecurityHelper.Sign(PgmInstanceId(cmpCtx), string.Empty, value, SecureTokenHelper.SecurityMode.Sign, context); } + private string GetSecureSignedHashedToken(string cmpCtx, TokenValue tokenValue, IGxContext context) + { + return WebSecurityHelper.Sign(PgmInstanceId(cmpCtx), string.Empty, tokenValue, SecureTokenHelper.SecurityMode.Sign, context); + } + protected bool VerifySecureSignedToken(string cmpCtx, Object value, string pic, string signedToken, IGxContext context) { GxUserType SDT = value as GxUserType; diff --git a/dotnet/src/dotnetframework/GxClasses/Security/WebSecurity.cs b/dotnet/src/dotnetframework/GxClasses/Security/WebSecurity.cs index 27eb82564..01e78013f 100644 --- a/dotnet/src/dotnetframework/GxClasses/Security/WebSecurity.cs +++ b/dotnet/src/dotnetframework/GxClasses/Security/WebSecurity.cs @@ -32,6 +32,15 @@ public static string Sign(string pgmName, string issuer, string value, SecurityM { return SecureTokenHelper.Sign(new WebSecureToken { ProgramName = pgmName, Issuer = issuer, Value = string.IsNullOrEmpty(value) ? string.Empty: StripInvalidChars(value) }, mode, GetSecretKey(context)); } + internal static string Sign(string pgmName, string issuer, TokenValue tokenValue, SecurityMode mode, IGxContext context) + { + return SecureTokenHelper.Sign(new WebSecureToken { + ProgramName = pgmName, + Issuer = issuer, + ValueType = tokenValue.ValueType, + Value = string.IsNullOrEmpty(tokenValue.Value) ? string.Empty : StripInvalidChars(tokenValue.Value) }, + mode, GetSecretKey(context)); + } private static string GetSecretKey(IGxContext context) { @@ -88,33 +97,65 @@ public static bool Verify(string pgmName, string issuer, string value, string jw internal static bool VerifySecureSignedSDTToken(string cmpCtx, IGxCollection value, string signedToken, IGxContext context) { - WebSecureToken Token = SecureTokenHelper.getWebSecureToken(signedToken, GetSecretKey(context)); - if (Token == null) + WebSecureToken token = SecureTokenHelper.getWebSecureToken(signedToken, GetSecretKey(context)); + if (token == null) return false; - IGxCollection PayloadObject = (IGxCollection)value.Clone(); - PayloadObject.FromJSonString(Token.Value); - return GxUserType.IsEqual(value, PayloadObject); + if (token.ValueType == SecureTokenHelper.ValueTypeHash) + { + return VerifyTokenHash(value.ToJSonString(), token); + } + else + { + IGxCollection PayloadObject = (IGxCollection)value.Clone(); + PayloadObject.FromJSonString(token.Value); + return GxUserType.IsEqual(value, PayloadObject); + } } internal static bool VerifySecureSignedSDTToken(string cmpCtx, GxUserType value, string signedToken, IGxContext context) { - WebSecureToken Token = SecureTokenHelper.getWebSecureToken(signedToken, GetSecretKey(context)); - if (Token == null) + WebSecureToken token = SecureTokenHelper.getWebSecureToken(signedToken, GetSecretKey(context)); + if (token == null) return false; - GxUserType PayloadObject = (GxUserType)value.Clone(); - PayloadObject.FromJSonString(Token.Value); - return GxUserType.IsEqual(value, PayloadObject); - } + if (token.ValueType == ValueTypeHash) + { + return VerifyTokenHash(value.ToJSonString(), token); + } + else + { + GxUserType PayloadObject = (GxUserType)value.Clone(); + PayloadObject.FromJSonString(token.Value); + return GxUserType.IsEqual(value, PayloadObject); + } + } + private static bool VerifyTokenHash(string payloadJsonString, WebSecureToken token) + { + string hash = GetHash(payloadJsonString); + if (hash != token.Value) + { + GXLogging.Error(_log, $"WebSecurity Token Verification error - Hash mismatch '{hash}' <> '{token.Value}'"); + GXLogging.Debug(_log, "Payload TokenOriginalValue: " + payloadJsonString); + return false; + } + return true; + } + } + internal class TokenValue + { + internal string Value { get; set; } + internal string ValueType { get; set; } } + [SecuritySafeCritical] public static class SecureTokenHelper { - static readonly IGXLogger _log = GXLoggerFactory.GetLogger(typeof(SecureTokenHelper).FullName); + internal const string ValueTypeHash = "hash"; + const int MaxTokenValueLength = 1024; - public enum SecurityMode + public enum SecurityMode { Sign, SignEncrypt, @@ -144,6 +185,7 @@ internal static WebSecureToken getWebSecureToken(string signedToken, string secr WebSecureToken outToken = new WebSecureToken(); var claims = handler.ValidateToken(signedToken, validationParameters, out securityToken); outToken.Value = claims.Identities.First().Claims.First(c => c.Type == WebSecureToken.GXVALUE).Value; + outToken.ValueType = claims.Identities.First().Claims.First(c => c.Type == WebSecureToken.GXVALUE_TYPE)?.Value ?? string.Empty; return outToken; } } @@ -161,7 +203,8 @@ public static string Sign(WebSecureToken token, SecurityMode mode, string secret new Claim(WebSecureToken.GXISSUER, token.Issuer), new Claim(WebSecureToken.GXPROGRAM, token.ProgramName), new Claim(WebSecureToken.GXVALUE, token.Value), - new Claim(WebSecureToken.GXEXPIRATION, token.Expiration.Subtract(new DateTime(1970, 1, 1)).TotalSeconds.ToString()) + new Claim(WebSecureToken.GXEXPIRATION, token.Expiration.Subtract(new DateTime(1970, 1, 1)).TotalSeconds.ToString()), + new Claim(WebSecureToken.GXVALUE_TYPE, token.ValueType ?? string.Empty) }), notBefore: DateTime.UtcNow, expires: token.Expiration, @@ -195,6 +238,7 @@ internal static bool Verify(string jwtToken, WebSecureToken outToken, string sec System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod, null, handler, new object[] { jwtToken, validationParameters }); Validators.ValidateIssuerSecurityKey(jwtSecurityToken.SigningKey, jwtSecurityToken, validationParameters); + outToken.Expiration = new DateTime(1970, 1, 1).AddSeconds(Double.Parse(jwtSecurityToken.Claims.First(c => c.Type == WebSecureToken.GXEXPIRATION).Value)); outToken.ProgramName = jwtSecurityToken.Claims.First(c => c.Type == WebSecureToken.GXPROGRAM).Value; outToken.Issuer = jwtSecurityToken.Claims.First(c => c.Type == WebSecureToken.GXISSUER).Value; @@ -204,14 +248,42 @@ internal static bool Verify(string jwtToken, WebSecureToken outToken, string sec } catch (Exception e) { - GXLogging.ErrorSanitized(_log, string.Format("Web Token verify failed for Token '{0}'", jwtToken), e); + GXLogging.Error(_log, string.Format("Web Token verify failed for Token '{0}'", jwtToken), e); } } return ok; - } - } + } + internal static TokenValue GetTokenValue(IGxJSONSerializable obj) + { + + string jsonString = obj.ToJSonString(); - [DataContract] + if (jsonString.Length > MaxTokenValueLength) + { + string hash = GetHash(jsonString); + GXLogging.Debug(_log, $"GetTokenValue: TokenValue is too long, using hash: {hash} instead of original value."); + GXLogging.Debug(_log, $"Server TokenOriginalValue:" + jsonString); + return new TokenValue() { Value = hash, ValueType = ValueTypeHash }; + } + else + { + GXLogging.Debug(_log, $"GetTokenValue:" + jsonString); + return new TokenValue() { Value = jsonString }; + } + } + internal static string GetHash(string jsonString) + { + using (var sha256 = System.Security.Cryptography.SHA256.Create()) + { + byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(jsonString)); + jsonString = Convert.ToBase64String(hashBytes); + return jsonString; + } + } + + } + + [DataContract] public abstract class SecureToken : IGxJSONSerializable { public abstract string ToJSonString(); @@ -241,8 +313,9 @@ public class WebSecureToken: SecureToken internal const string GXPROGRAM = "gx-pgm"; internal const string GXVALUE = "gx-val"; internal const string GXEXPIRATION = "gx-exp"; + internal const string GXVALUE_TYPE = "gx-val-type"; - [DataMember(Name = GXISSUER, IsRequired = true, EmitDefaultValue = false)] + [DataMember(Name = GXISSUER, IsRequired = true, EmitDefaultValue = false)] public string Issuer { get; set; } [DataMember(Name = GXPROGRAM, IsRequired = true, EmitDefaultValue = false)] @@ -254,7 +327,9 @@ public class WebSecureToken: SecureToken [DataMember(Name = GXEXPIRATION, EmitDefaultValue = false)] public DateTime Expiration { get; set; } - public WebSecureToken() + [DataMember(Name = GXVALUE_TYPE, EmitDefaultValue = false)] + public string ValueType { get; set; } + public WebSecureToken() { Expiration = DateTime.Now.AddDays(15); } @@ -267,6 +342,7 @@ public override bool FromJSonString(string s) this.Value = wt.Value; this.ProgramName = wt.ProgramName; this.Issuer = wt.Issuer; + this.ValueType = wt.ValueType; return true; } catch (Exception)