Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot alter authorisation headers when verifying pact #702

Closed
lkankowski opened this issue Dec 6, 2024 · 6 comments
Closed

Cannot alter authorisation headers when verifying pact #702

lkankowski opened this issue Dec 6, 2024 · 6 comments

Comments

@lkankowski
Copy link

lkankowski commented Dec 6, 2024

Hi.
I have a code where requests payload is signed with a secret.
I am aware of https://docs.pact.io/provider/handling_auth#4-modify-the-request-to-use-real-credentials, but it does not apply to signing content.
Ideal solution would be some request filter, so I could get real content, calculate signature and generate appropriate header.
Another solution is to give ability in provider setting state access to real request, but this is meant for different thing.

@YOU54F
Copy link
Member

YOU54F commented Dec 6, 2024

request filters work by having a proxy application, that receives the requests from the mock consumer, and allowing callbacks, to modify the request contents before being sent to the application under test.

Ideally its provided to the user by the framework for ease of use, but there should be nothing stopping you from doing that yourself

@lkankowski
Copy link
Author

Here is my sample contract verification in PHP:
https://gist.github.com/lkankowski/2cee67803f2c45c274b9609af26ff5ab

It would be nice to have something like:

        $verifier->requestCallback(function (Request $request): void {
            $request->headers->set('Authorization', $this->getTokenForPayload($request->getContent()));
        });
      
        $verify = $verifier->verify();

I've manage to do some workaround inside the app, which you can check here - https://gist.github.com/lkankowski/2cee67803f2c45c274b9609af26ff5ab#file-pactrequestsignsubscriber-php
but I don't think it is elegant way to interfere into the app.

@tienvx
Copy link
Contributor

tienvx commented Dec 9, 2024

It would be nice to have something like:

$verifier->requestCallback(function (Request $request): void {
            $request->headers->set('Authorization', $this->getTokenForPayload($request->getContent()));
});

I assume you are using v10. I think it's currently not possible to do that, because pact-reference (pact-core) doesn't have a way to implement it.

I've manage to do some workaround inside the app

I think your solution is acceptable since pact-php doesn't provide a way to do it.

@tienvx
Copy link
Contributor

tienvx commented Dec 9, 2024

Or, you can create a simple proxy to do it for you. Here is an example code in go:

package main

import (
	"encoding/base64"
	"flag"
	"fmt"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
)

func proxyHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Received request", r.Method, r.URL)
	proxy := &httputil.ReverseProxy{
		Rewrite: func(r *httputil.ProxyRequest) {
                        dest := fmt.Sprintf("%s:%s", os.Getenv("DEST_HOST"), os.Getenv("DEST_PORT"))
			target, _ := url.Parse(dest)
			r.SetURL(target)
                        sig := "some signature calculation" # based on r.In
			r.Out.Header.Add("Authorization", sig)
		},
	}

	proxy.ServeHTTP(w, r)
}

func main() {
	var help = flag.Bool("help", false, "help")
	var host = flag.String("host", "127.0.0.1", "proxy host")
	var port = flag.String("port", "8888", "proxy port")
	flag.Parse()

	if *help {
		fmt.Println("Pact Proxy")
		fmt.Println("")
		fmt.Println("This program attach Authorization header and proxy requests to the real selenium server.")
		fmt.Println("")
		fmt.Println("Usage: env DEST_HOST=192.168.1.11 DEST_PORT=9999 ./main --host=127.0.0.1 --port=8888")
		os.Exit(1)
	}

	var addr = fmt.Sprintf("%s:%s", *host, *port)

	http.HandleFunc("/", proxyHandler)
	log.Println("Starting proxy server on", addr)
	if err := http.ListenAndServe(addr, nil); err != nil {
		log.Fatalf("Can not start proxy: %s", err)
	}
}

@tienvx
Copy link
Contributor

tienvx commented Dec 9, 2024

Or you can use the provider state approach.

Consumer side:

$request = new ConsumerRequest();
$request
    // ...
    ->addHeader('Authorization', $this->matcher->fromProviderState($this->matcher->string(), '${signature}'));

$response = new ProviderResponse();
// ...
$config = new MockServerConfig();
// ...

$builder = new InteractionBuilder($config);
$builder
    ->uponReceiving('A request with uuid cfeda9c2-fc1a-42ae-862a-10f7192eb96e')
    ->with($request)
    ->willRespondWith($response); 

Provider side:

(Look like you are using Symfony, so I will suggest the code from my own bundle to make life easier)

namespace App\StateHandler;

use Tienvx\Bundle\PactProviderBundle\Attribute\AsStateHandler;
use Tienvx\Bundle\PactProviderBundle\Model\StateValues;
use Tienvx\Bundle\PactProviderBundle\StateHandler\SetUpInterface;
use Tienvx\Bundle\PactProviderBundle\StateHandler\TearDownInterface;

#[AsStateHandler(state: 'A request with uuid cfeda9c2-fc1a-42ae-862a-10f7192eb96e')]
class UserHandler implements SetUpInterface, TearDownInterface
{
    public function setUp(array $params): ?StateValues
    {
        return new StateValues([
            'signature' => 'hard coded signature for this request only',
        ]);
    }

    public function tearDown(array $params): void
    {
    }
}

This approach probably not what you want. But at least you can find that my bundle is useful and can be used in other places.

@lkankowski
Copy link
Author

@tienvx Thank you for all comments and ideas. Nice to know about your bundle :)

The answer about "it is impossible in v10" is enough to close this issue, as there are enough workarounds. But, stil I think there should be a nicer way... :)

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

No branches or pull requests

3 participants