digest

package module
v0.1.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 8, 2022 License: MIT Imports: 15 Imported by: 67

README

HTTP Digest Access Authentication

go.dev reference

This package provides a http.RoundTripper implementation which re-uses digest challenges

package main

import (
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := &http.Client{
		Transport: &digest.Transport{
			Username: "foo",
			Password: "bar",
		},
	}
	res, err := client.Get("http://localhost:8080/some_outdated_service")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Using Cookies

If you're using an http.CookieJar the digest.Transport needs a reference to it.

package main

import (
	"net/http"
	"net/http/cookiejar"

	"github.com/icholy/digest"
)

func main() {
	jar, _ := cookiejar.New(nil)
	client := &http.Client{
		Transport: &digest.Transport{
			Jar:      jar,
			Username: "foo",
			Password: "bar",
		},
	}
	res, err := client.Get("http://localhost:8080/digest_with_cookies")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Custom Authenticate Header

package main

import (
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := &http.Client{
		Transport: &digest.Transport{
			Username: "foo",
			Password: "bar",
			FindChallenge: func(h http.Header) (*digest.Challenge, error) {
				value := h.Get("Custom-Authenticate-Header")
				return digest.ParseChallenge(value)
			},
		},
	}
	res, err := client.Get("http://localhost:8080/non_compliant")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Override Digest Options

package main

import (
	"fmt"
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := &http.Client{
		Transport: &digest.Transport{
			Digest: func(req *http.Request, chal *digest.Challenge, opt digest.Options) (*digest.Credentials, error) {
				switch req.URL.Hostname() {
				case "badauth.org":
					opt.Username = "foo"
					opt.Password = "bar"
				case "poorsecurity.com":
					opt.Username = "zoo"
					opt.Password = "boo"
				default:
					return nil, fmt.Errorf("unsuported host: %q", req.URL)
				}
				return digest.Digest(chal, opt)
			},
		},
	}
	res, err := client.Get("http://poorsecurity.com/legacy.php")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
}

Low Level API

func main() {
  // get the challenge from a 401 response
  header := res.Header.Get("WWW-Authenticate")
  chal, _ := digest.ParseChallenge(header)

  // use it to create credentials for the next request
  cred, _ := digest.Digest(chal, digest.Options{
    Username: "foo",
    Password: "bar",
    Method:   req.Method,
    URI:      req.URL.RequestURI(),
    GetBody:  req.GetBody,
    Count:    1,
  })
  req.Header.Set("Authorization", cred.String())

  // if you use the same challenge again, you must increment the Count
  cred2, _ := digest.Digest(chal, digest.Options{
    Username: "foo",
    Password: "bar",
    Method:   req2.Method,
    URI:      req2.URL.RequestURI(),
    GetBody:  req2.GetBody,
    Count:    2,
  })
  req2.Header.Set("Authorization", cred.String())
}

Documentation

Index

Examples

Constants

View Source
const Prefix = "Digest "

Prefix for digest authentication headers

Variables

View Source
var ErrNoChallenge = errors.New("digest: no challenge found")

ErrNoChallenge indicates that no WWW-Authenticate headers were found.

Functions

func CanDigest

func CanDigest(c *Challenge) bool

CanDigest checks if the algorithm and qop are supported

func IsDigest

func IsDigest(header string) bool

IsDigest returns true if the header value is a digest auth header

Types

type Challenge

type Challenge struct {
	Realm     string
	Domain    []string
	Nonce     string
	Opaque    string
	Stale     bool
	Algorithm string
	QOP       []string
	Charset   string
	Userhash  bool
}

Challenge is a challenge sent in the WWW-Authenticate header

func FindChallenge

func FindChallenge(h http.Header) (*Challenge, error)

FindChallenge returns the first supported challenge in the headers

func ParseChallenge

func ParseChallenge(s string) (*Challenge, error)

ParseChallenge parses the WWW-Authenticate header challenge

func (*Challenge) String

func (c *Challenge) String() string

String returns the foramtted header value

func (*Challenge) SupportsQOP

func (c *Challenge) SupportsQOP(qop string) bool

SupportsQOP returns true if the challenge advertises support for the provided qop value

type Credentials

type Credentials struct {
	Username  string
	Realm     string
	Nonce     string
	URI       string
	Response  string
	Algorithm string
	Cnonce    string
	Opaque    string
	QOP       string
	Nc        int
	Userhash  bool
}

Credentials is a parsed version of the Authorization header

func Digest

func Digest(chal *Challenge, o Options) (*Credentials, error)

Digest creates credentials from a challenge and request options. Note: if you want to re-use a challenge, you must increment the Count.

Example
package main

import (
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	// The first request will return a 401 Unauthorized response
	req, _ := http.NewRequest(http.MethodGet, "http://httpbin.org/digest-auth/auth/foo/bar/SHA-512", nil)
	res, _ := http.DefaultClient.Do(req)
	// Create digest credentials from the request challenge
	chal, _ := digest.FindChallenge(res.Header)
	cred, _ := digest.Digest(chal, digest.Options{
		Method:   req.Method,
		URI:      req.URL.RequestURI(),
		Username: "foo",
		Password: "bar",
	})
	// Try the request again with the credentials
	req.Header.Set("Authorization", cred.String())
	res, _ = http.DefaultClient.Do(req)
	println(res.Status)
}
Output:

func ParseCredentials

func ParseCredentials(s string) (*Credentials, error)

ParseCredentials parses the Authorization header value into credentials

func (*Credentials) String

func (c *Credentials) String() string

String formats the credentials into the header format

type Options

type Options struct {
	Method   string
	URI      string
	GetBody  func() (io.ReadCloser, error)
	Count    int
	Username string
	Password string

	// used for testing
	Cnonce string
}

Options for creating a credentials

type Transport

type Transport struct {
	Username string
	Password string

	// Digest computes the digest credentials.
	// If nil, the Digest function is used.
	Digest func(*http.Request, *Challenge, Options) (*Credentials, error)

	// FindChallenge extracts the challenge from the request headers.
	// If nil, the FindChallenge function is used.
	FindChallenge func(http.Header) (*Challenge, error)

	// Transport specifies the mechanism by which individual
	// HTTP requests are made.
	// If nil, DefaultTransport is used.
	Transport http.RoundTripper

	// Jar specifies the cookie jar.
	//
	// The Jar is used to insert relevant cookies into every
	// outbound Request and is updated with the cookie values
	// of every inbound Response. The Jar is consulted for every
	// redirect that the Client follows.
	//
	// If Jar is nil, cookies are only sent if they are explicitly
	// set on the Request.
	Jar http.CookieJar

	// NoReuse prevents the transport from reusing challenges.
	NoReuse bool
	// contains filtered or unexported fields
}

Transport implements http.RoundTripper

Example
package main

import (
	"net/http"

	"github.com/icholy/digest"
)

func main() {
	client := http.Client{
		Transport: &digest.Transport{
			Username: "foo",
			Password: "bar",
		},
	}
	res, _ := client.Get("http://httpbin.org/digest-auth/auth/foo/bar/SHA-512")
	println(res.Status)
}
Output:

func (*Transport) CloseIdleConnections added in v0.1.21

func (t *Transport) CloseIdleConnections()

CloseIdleConnections delegates the call to the underlying transport.

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip will try to authorize the request using a cached challenge. If that doesn't work and we receive a 401, we'll try again using that challenge.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL