Skip to content

Conversation

@Kwok-he-Chu
Copy link
Member

@Kwok-he-Chu Kwok-he-Chu commented Nov 18, 2025

PR-2 of #1204

Description

This PR introduces the mustache.templates needed for the upgrade of the OpenApiGenerator to v7.16.0 used in the Adyen sdk-automation to generate .NET models & services from the Adyen OpenAPI Specifications.

We've always removed the .NET6.0 (= end of life support) workflow as we'll continue on .NET 8.0+

Remove old mustache templates
@Kwok-he-Chu Kwok-he-Chu requested review from a team as code owners November 18, 2025 15:53
@Kwok-he-Chu Kwok-he-Chu changed the base branch from main to v7-generator/1-add-core-classes November 18, 2025 15:53
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a significant upgrade, replacing old templates with new ones for the generichost library to support OpenAPI Generator v7.16.0. The changes are extensive and introduce a new structure for C# code generation. My review of the new mustache templates has identified a few critical and high-severity issues related to null handling, incorrect logic in authentication flows, and attempts to instantiate abstract classes. These could lead to runtime exceptions or unexpected behavior in the generated code. I've provided detailed feedback and code suggestions to improve the robustness and correctness of the new templates.

Comment on lines +417 to +458
catch (global::System.FormatException)
{
StringReader str = new StringReader(pvkstr);

//-------- read PEM encryption info. lines and extract salt -----
if (!str.ReadLine(){{nrt!}}.StartsWith("Proc-Type: 4,ENCRYPTED")) // TODO: what do we do here if ReadLine is null?
return null;

String saltline = str.ReadLine(){{nrt!}}; // TODO: what do we do here if ReadLine is null?
if (!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,"))
return null;

String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
byte[] salt = new byte[saltstr.Length / 2];
for (int i = 0; i < salt.Length; i++)
salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);

if (!(str.ReadLine() == ""))
return null;

//------ remaining b64 data is encrypted RSA key ----
String encryptedstr = str.ReadToEnd();

try
{ //should have b64 encrypted RSA key now
binkey = Convert.FromBase64String(encryptedstr);
}
catch (global::System.FormatException)
{ //data is not in base64 format
return null;
}

// TODO: what do we do here if keyPassPhrase is null?
byte[] deskey = GetEncryptedKey(salt, keyPassPhrase{{nrt!}}, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
if (deskey == null)
return null;

//------ Decrypt the encrypted 3des-encrypted RSA private key ------
byte[]{{nrt?}} rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV

return rsakey;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This catch block has several potential NullReferenceException issues and lacks proper error handling for encrypted keys without a passphrase.

  1. str.ReadLine() can return null, but the null-forgiving operator (!) is used, which can lead to a crash. You should use null-conditional access (?.).
  2. If an encrypted key is provided without a keyPassPhrase, the code will crash at line 450. There should be a check for a null keyPassPhrase before attempting to decrypt the key.

I suggest refactoring this block for safer null handling and clearer error reporting.

            catch (global::System.FormatException)
            {
                StringReader str = new StringReader(pvkstr);

                //-------- read PEM encryption info. lines and extract salt -----
                if (str.ReadLine()?.StartsWith("Proc-Type: 4,ENCRYPTED") != true)
                    return null;
                
                String saltline = str.ReadLine();
                if (saltline?.StartsWith("DEK-Info: DES-EDE3-CBC,") != true)
                    return null;
                
                String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim();
                byte[] salt = new byte[saltstr.Length / 2];
                for (int i = 0; i < salt.Length; i++)
                    salt[i] = Convert.ToByte(saltstr.Substring(i * 2, 2), 16);

                if (!(str.ReadLine() == ""))
                    return null;

                //------ remaining b64 data is encrypted RSA key ----
                String encryptedstr = str.ReadToEnd();

                try
                {   //should have b64 encrypted RSA key now
                    binkey = Convert.FromBase64String(encryptedstr);
                }
                catch (global::System.FormatException)
                {   //data is not in base64 format
                    return null;
                }

                if (keyPassPhrase == null)
                    throw new ArgumentException("A passphrase is required for an encrypted private key.", nameof(keyPassPhrase));

                byte[] deskey = GetEncryptedKey(salt, keyPassPhrase, 1, 2);    // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
                if (deskey == null)
                    return null;

                //------ Decrypt the encrypted 3des-encrypted RSA private key ------
                byte[]{{nrt?}} rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV

                return rsakey;
            }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template is used in two test files, I've removed them here

Comment on lines +81 to +85
/// <inheritdoc/>
public bool IsValidHmacSignature(string json, string hmacSignature)
{
return {{packageName}}.{{corePackageName}}.Utilities.HmacValidatorUtility.IsHmacSignatureValid(hmacSignature, _adyenHmacKey, json);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The current implementation of IsValidHmacSignature has a critical flaw: it uses the _adyenHmacKey field which is never initialized, causing HMAC validation to always use a null key.

To fix this and follow best practices, I recommend the following changes:

  1. Store the provider: In the {{apiName}}Handler constructor, store the injected ITokenProvider<HmacKeyToken> in a private readonly field (e.g., _hmacKeyProvider).
  2. Make validation async: Change IsValidHmacSignature to be an async method (e.g., IsValidHmacSignatureAsync) in both the {{interfacePrefix}}{{apiName}}Handler interface and the {{apiName}}Handler class. This is crucial to avoid blocking on I/O when retrieving the key.
  3. Implement async logic: In the new async method, await the key from _hmacKeyProvider.GetAsync() and then perform the validation.

This approach ensures correct, non-blocking HMAC validation.

Comment on lines +74 to +75
default:
break;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The default case in this switch statement does nothing, which means if an unsupported OAuthFlow is provided, the _grantType field will remain uninitialized. This will likely cause a null value to be sent in the token request later. It's better to throw an exception for unsupported flows to fail early and clearly.

                default:
                    throw new NotSupportedException($"The OAuth flow '{flow}' is not supported.");

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mustache template is unused

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gcatanese Q: Do we leave this in? I've only included it for completeness as part of the v7 generator upgrade

@Adyen Adyen deleted a comment from gemini-code-assist bot Nov 18, 2025
@Kwok-he-Chu Kwok-he-Chu changed the title OpenAPIGenerator upgrade to v7.16.0 - Added new mustache templates OpenAPIGenerator upgrade to v7.16.0 - [2] Added new mustache templates Nov 18, 2025
@Kwok-he-Chu Kwok-he-Chu self-assigned this Nov 18, 2025
Kwok-he-Chu added a commit that referenced this pull request Nov 19, 2025
Remove unused templates ApiTests, DependencyInjectionTests
#1208 (comment)
@Kwok-he-Chu
Copy link
Member Author

@gcatanese The mustache templates include the generated templates for the classes in the /Core-folder, these templates can be removed (e.g. Options.mustache template etc.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants