From ca7dddf820b67da19606a24da05022450e655289 Mon Sep 17 00:00:00 2001 From: Sathya Date: Mon, 3 Aug 2026 01:07:11 -0400 Subject: [PATCH] Support authenticated NuGet downloads Allow private NuGet repositories to provide request headers. Document supported auth schemes and add request construction tests. --- README.md | 16 ++++- deps/windows-install.py | 131 ++++++++++++++++++++--------------- deps/windows-install.spec.py | 60 ++++++++++++++++ 3 files changed, 151 insertions(+), 56 deletions(-) create mode 100644 deps/windows-install.spec.py diff --git a/README.md b/README.md index ba3f4f15..2640d6c7 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,21 @@ Using Alpine Linux? Check out the [docs](https://github.com/Blizzard/node-rdkafk ### Windows -Windows build **is not** compiled from `librdkafka` source but it is rather linked against the appropriate version of [NuGet librdkafka.redist](https://www.nuget.org/packages/librdkafka.redist/) static binary that gets downloaded from `https://globalcdn.nuget.org/packages/librdkafka.redist.2.12.0.nupkg` during installation. This download link can be changed using the environment variable `NODE_RDKAFKA_NUGET_BASE_URL` that defaults to `https://globalcdn.nuget.org/packages/` when it's no set. +Windows build **is not** compiled from `librdkafka` source but it is rather +linked against the appropriate version of +[NuGet librdkafka.redist](https://www.nuget.org/packages/librdkafka.redist/) +static binary that gets downloaded from +`https://globalcdn.nuget.org/packages/librdkafka.redist.2.12.0.nupkg` during +installation. This download link can be changed using the environment variable +`NODE_RDKAFKA_NUGET_BASE_URL`, which defaults to +`https://globalcdn.nuget.org/packages/` when it is not set. + +For private repositories, set `NODE_RDKAFKA_NUGET_HEADERS` to a JSON object of +HTTP request headers. This supports the authentication scheme required by the +repository, for example `{"Authorization":"Basic "}`, +`{"Authorization":"Bearer "}`, or +`{"X-JFrog-Art-Api":""}`. Basic authentication credentials must be +Base64-encoded in the standard `username:password` format. Requirements: * [node-gyp for Windows](https://github.com/nodejs/node-gyp#on-windows) diff --git a/deps/windows-install.py b/deps/windows-install.py index f282df63..31cae9d9 100644 --- a/deps/windows-install.py +++ b/deps/windows-install.py @@ -1,73 +1,94 @@ -librdkafkaVersion = '' -# read librdkafka version from package.json +import errno +import glob import json import os -import glob +import shutil +import ssl +import zipfile -with open('../package.json') as f: - librdkafkaVersion = json.load(f)['librdkafka'] -librdkafkaWinSufix = '7' if librdkafkaVersion == '0.11.5' else ''; - -depsPrecompiledDir = '../deps/precompiled' -depsIncludeDir = '../deps/include' -buildReleaseDir = 'Release' - -# alternative: 'https://api.nuget.org/v3-flatcontainer/librdkafka.redist/{}/librdkafka.redist.{}.nupkg'.format(librdkafkaVersion, librdkafkaVersion) -env_dist = os.environ -downloadBaseUrl = env_dist['NODE_RDKAFKA_NUGET_BASE_URL'] if 'NODE_RDKAFKA_NUGET_BASE_URL' in env_dist else 'https://globalcdn.nuget.org/packages/' -librdkafkaNugetUrl = downloadBaseUrl + 'librdkafka.redist.{}.nupkg'.format(librdkafkaVersion) -print('download librdkafka form ' + librdkafkaNugetUrl) -outputDir = 'librdkafka.redist' -outputFile = outputDir + '.zip' -dllPath = outputDir + '/runtimes/win{}-x64/native'.format(librdkafkaWinSufix) -libPath = outputDir + '/build/native/lib/win{}/x64/win{}-x64-Release/v142'.format(librdkafkaWinSufix, librdkafkaWinSufix) -includePath = outputDir + '/build/native/include/librdkafka' - -# download librdkafka from nuget try: # For Python 3.0 and later - from urllib.request import urlopen + from urllib.request import Request, urlopen except ImportError: # Fall back to Python 2's urllib2 - from urllib2 import urlopen -import ssl + from urllib2 import Request, urlopen + + +def createdir(dir): + try: + os.makedirs(dir) + except OSError as e: + if errno.EEXIST != e.errno: + raise -filedata = urlopen(librdkafkaNugetUrl, context=ssl._create_unverified_context()) -datatowrite = filedata.read() -with open(outputFile, 'wb') as f: +def createNugetRequest(url, environ): + headers = json.loads(environ.get('NODE_RDKAFKA_NUGET_HEADERS', '{}')) + if not isinstance(headers, dict): + raise ValueError('NODE_RDKAFKA_NUGET_HEADERS must be a JSON object') + return Request(url, headers=headers) + + +def main(): + # read librdkafka version from package.json + with open('../package.json') as f: + librdkafkaVersion = json.load(f)['librdkafka'] + librdkafkaWinSufix = '7' if librdkafkaVersion == '0.11.5' else '' + + depsPrecompiledDir = '../deps/precompiled' + depsIncludeDir = '../deps/include' + buildReleaseDir = 'Release' + + env_dist = os.environ + downloadBaseUrl = env_dist.get( + 'NODE_RDKAFKA_NUGET_BASE_URL', + 'https://globalcdn.nuget.org/packages/' + ) + packageName = 'librdkafka.redist.{}.nupkg'.format(librdkafkaVersion) + librdkafkaNugetUrl = downloadBaseUrl + packageName + print('download librdkafka from ' + librdkafkaNugetUrl) + outputDir = 'librdkafka.redist' + outputFile = outputDir + '.zip' + dllPath = outputDir + '/runtimes/win{}-x64/native'.format( + librdkafkaWinSufix + ) + libPath = outputDir + '/build/native/lib/win{}/x64/' \ + 'win{}-x64-Release/v142'.format( + librdkafkaWinSufix, librdkafkaWinSufix + ) + includePath = outputDir + '/build/native/include/librdkafka' + + # download librdkafka from nuget + request = createNugetRequest(librdkafkaNugetUrl, env_dist) + filedata = urlopen(request, context=ssl._create_unverified_context()) + + datatowrite = filedata.read() + with open(outputFile, 'wb') as f: f.write(datatowrite) -# extract package -import zipfile -zip_ref = zipfile.ZipFile(outputFile, 'r') -zip_ref.extractall(outputDir) -zip_ref.close() + # extract package + zip_ref = zipfile.ZipFile(outputFile, 'r') + zip_ref.extractall(outputDir) + zip_ref.close() -# copy files -import shutil, os, errno + createdir(depsPrecompiledDir) + createdir(depsIncludeDir) + createdir(buildReleaseDir) -def createdir(dir): - try: - os.makedirs(dir) - except OSError as e: - if errno.EEXIST != e.errno: - raise + shutil.copy2(libPath + '/librdkafka.lib', depsPrecompiledDir) + shutil.copy2(libPath + '/librdkafkacpp.lib', depsPrecompiledDir) -createdir(depsPrecompiledDir) -createdir(depsIncludeDir) -createdir(buildReleaseDir) + shutil.copy2(includePath + '/rdkafka.h', depsIncludeDir) + shutil.copy2(includePath + '/rdkafkacpp.h', depsIncludeDir) -shutil.copy2(libPath + '/librdkafka.lib', depsPrecompiledDir) -shutil.copy2(libPath + '/librdkafkacpp.lib', depsPrecompiledDir) + # copy all the required dlls + for filename in glob.glob(os.path.join(dllPath, '*.dll')): + shutil.copy2(filename, buildReleaseDir) -shutil.copy2(includePath + '/rdkafka.h', depsIncludeDir) -shutil.copy2(includePath + '/rdkafkacpp.h', depsIncludeDir) + # clean up + os.remove(outputFile) + shutil.rmtree(outputDir) -# copy all the required dlls -for filename in glob.glob(os.path.join(dllPath, '*.dll')): - shutil.copy2(filename, buildReleaseDir) -# clean up -os.remove(outputFile) -shutil.rmtree(outputDir) +if __name__ == '__main__': + main() diff --git a/deps/windows-install.spec.py b/deps/windows-install.spec.py new file mode 100644 index 00000000..7f33c0f0 --- /dev/null +++ b/deps/windows-install.spec.py @@ -0,0 +1,60 @@ +import base64 +import importlib.util +import os +import unittest + + +SCRIPT_PATH = os.path.join(os.path.dirname(__file__), 'windows-install.py') +SPEC = importlib.util.spec_from_file_location('windows_install', SCRIPT_PATH) +WINDOWS_INSTALL = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(WINDOWS_INSTALL) + + +class CreateNugetRequestTest(unittest.TestCase): + def test_creates_request_without_headers(self): + request = WINDOWS_INSTALL.createNugetRequest( + 'https://example.test/package.nupkg', {} + ) + + self.assertEqual(request.header_items(), []) + + def test_adds_configured_authentication_headers(self): + environ = { + 'NODE_RDKAFKA_NUGET_HEADERS': ( + '{"Authorization":"Bearer token","X-Api-Key":"secret"}' + ) + } + + request = WINDOWS_INSTALL.createNugetRequest( + 'https://example.test/package.nupkg', environ + ) + + self.assertEqual(request.get_header('Authorization'), 'Bearer token') + self.assertEqual(request.get_header('X-api-key'), 'secret') + + def test_adds_base64_encoded_basic_authentication_header(self): + credentials = base64.b64encode(b'user:password').decode('ascii') + authorization = 'Basic ' + credentials + environ = { + 'NODE_RDKAFKA_NUGET_HEADERS': ( + '{"Authorization":"' + authorization + '"}' + ) + } + + request = WINDOWS_INSTALL.createNugetRequest( + 'https://example.test/package.nupkg', environ + ) + + self.assertEqual(request.get_header('Authorization'), authorization) + + def test_rejects_non_object_headers(self): + environ = {'NODE_RDKAFKA_NUGET_HEADERS': '["Authorization"]'} + + with self.assertRaisesRegex(ValueError, 'must be a JSON object'): + WINDOWS_INSTALL.createNugetRequest( + 'https://example.test/package.nupkg', environ + ) + + +if __name__ == '__main__': + unittest.main()