Repository: buildbot/buildbot

Issue:
Can't use secret interpolation in change_hook_dialects?
Trying to do something like
```
c['www'] = dict(
    port=8010,
    change_hook_dialects=dict(
       gitlab={
           'secret': util.Secret('my-webhook-token')
       },
    ),
    plugins=dict(waterfall_view={}, console_view={}, grid_view={}))
```
fails; the webhook complains 'HTTP 400 Invalid secret'.
Adding the print

```
--- a/master/buildbot/www/hooks/gitlab.py
+++ b/master/buildbot/www/hooks/gitlab.py
@@ -164,6 +164,7 @@ class GitLabHandler(BaseHookHandler):
             received_secret = request.getHeader(_HEADER_GITLAB_TOKEN)
             received_secret = bytes2unicode(received_secret)
             if received_secret != expected_secret:
+                log.msg("Wanted secret '%s' but got '%s'" % (expected_secret, received_secret))
                 raise ValueError("Invalid secret")
         try:
             content = request.content.read()
```
shows
```
...[_GenericHTTPChannelProtocol,0,10.10.169.29] Wanted secret 'Secret(my-webhook-token)' but got 'thesecret'
```

(Evidently master/buildbot/www/hooks/gitlab.py needs to render expected_secret?
https://github.com/buildbot/buildbot/issues/3627 has some tips on how to add that, but
not clear enough for me to follow at first try.)


Discussion:

Resolving patch:
diff --git a/master/buildbot/www/hooks/gitlab.py b/master/buildbot/www/hooks/gitlab.py
--- a/master/buildbot/www/hooks/gitlab.py
+++ b/master/buildbot/www/hooks/gitlab.py
@@ -19,8 +19,10 @@
 
 from dateutil.parser import parse as dateparse
 
+from twisted.internet.defer import inlineCallbacks
 from twisted.python import log
 
+from buildbot.process.properties import Properties
 from buildbot.util import bytes2unicode
 from buildbot.www.hooks.base import BaseHookHandler
 
@@ -151,6 +153,7 @@ def _process_merge_request_change(self, payload, event, codebase=None):
             changes[0]['codebase'] = codebase
         return changes
 
+    @inlineCallbacks
     def getChanges(self, request):
         """
         Reponds only to POST events and starts the build process
@@ -163,7 +166,12 @@ def getChanges(self, request):
         if expected_secret:
             received_secret = request.getHeader(_HEADER_GITLAB_TOKEN)
             received_secret = bytes2unicode(received_secret)
-            if received_secret != expected_secret:
+
+            p = Properties()
+            p.master = self.master
+            expected_secret_value = yield p.render(expected_secret)
+
+            if received_secret != expected_secret_value:
                 raise ValueError("Invalid secret")
         try:
             content = request.content.read()


Test patch:
diff --git a/master/buildbot/test/unit/test_www_hooks_gitlab.py b/master/buildbot/test/unit/test_www_hooks_gitlab.py
--- a/master/buildbot/test/unit/test_www_hooks_gitlab.py
+++ b/master/buildbot/test/unit/test_www_hooks_gitlab.py
@@ -18,6 +18,9 @@
 from twisted.internet import defer
 from twisted.trial import unittest
 
+from buildbot.plugins import util
+from buildbot.secrets.manager import SecretManager
+from buildbot.test.fake.secrets import FakeSecretStorage
 from buildbot.test.fake.web import FakeRequest
 from buildbot.test.fake.web import fakeMasterForHooks
 from buildbot.www import change_hook
@@ -998,9 +1001,19 @@ class TestChangeHookConfiguredWithSecret(unittest.TestCase):
     _SECRET = 'thesecret'
 
     def setUp(self):
+
+        self.master = fakeMasterForHooks(self)
+
+        fakeStorageService = FakeSecretStorage()
+        fakeStorageService.reconfigService(secretdict={"secret_key": self._SECRET})
+
+        self.secretService = SecretManager()
+        self.secretService.services = [fakeStorageService]
+        self.master.addService(self.secretService)
+
         self.changeHook = change_hook.ChangeHookResource(
-            dialects={'gitlab': {'secret': self._SECRET}},
-            master=fakeMasterForHooks(self))
+            dialects={'gitlab': {'secret': util.Secret("secret_key")}},
+            master=self.master)
 
     @defer.inlineCallbacks
     def test_missing_secret(self):