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

A better fix for the nc out of order problem #28

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Features
--------

* Supports HTTP Basic and HTTP Digest authentication.
* Supports proxy authentication
* Supports htpasswd and htdigest formatted files.
* Automatic reloading of password files.
* Pluggable interface for user/password storage.
Expand Down
32 changes: 32 additions & 0 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,35 @@ func JustCheck(auth AuthenticatorInterface, wrapped http.HandlerFunc) http.Handl
wrapped(w, &ar.Request)
})
}

func AuthenticateHeaderName(proxy bool) string {
if proxy {
return "Proxy-Authenticate"
}
return "WWW-Authenticate"
}

func AuthorizationHeaderName(proxy bool) string {
if proxy {
return "Proxy-Authorization"
}
return "Authorization"
}

func AuthenticationInfoHeaderName(proxy bool) string {
if proxy {
return "Proxy-Authentication-Info"
}
return "Authentication-Info"
}

func UnauthorizedStatusCode(proxy bool) int {
if proxy {
return http.StatusProxyAuthRequired
}
return http.StatusUnauthorized
}

func UnauthorizedStatusText(proxy bool) string {
return http.StatusText(UnauthorizedStatusCode(proxy))
}
14 changes: 9 additions & 5 deletions basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var (
)

type BasicAuth struct {
IsProxy bool
Realm string
Secrets SecretProvider
}
Expand All @@ -44,7 +45,7 @@ var _ = (AuthenticatorInterface)((*BasicAuth)(nil))
Supports MD5 and SHA1 password entries
*/
func (a *BasicAuth) CheckAuth(r *http.Request) string {
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
s := strings.SplitN(r.Header.Get(AuthorizationHeaderName(a.IsProxy)), " ", 2)
if len(s) != 2 || s[0] != "Basic" {
return ""
}
Expand Down Expand Up @@ -102,9 +103,8 @@ func compareMD5HashAndPassword(hashedPassword, password []byte) error {
(or requires reauthentication).
*/
func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", `Basic realm="`+a.Realm+`"`)
w.WriteHeader(401)
w.Write([]byte("401 Unauthorized\n"))
w.Header().Set(AuthenticateHeaderName(a.IsProxy), `Basic realm="`+a.Realm+`"`)
http.Error(w, UnauthorizedStatusText(a.IsProxy), UnauthorizedStatusCode(a.IsProxy))
}

/*
Expand All @@ -130,11 +130,15 @@ func (a *BasicAuth) NewContext(ctx context.Context, r *http.Request) context.Con
info := &Info{Username: a.CheckAuth(r), ResponseHeaders: make(http.Header)}
info.Authenticated = (info.Username != "")
if !info.Authenticated {
info.ResponseHeaders.Set("WWW-Authenticate", `Basic realm="`+a.Realm+`"`)
info.ResponseHeaders.Set(AuthenticateHeaderName(a.IsProxy), `Basic realm="`+a.Realm+`"`)
}
return context.WithValue(ctx, infoKey, info)
}

func NewBasicAuthenticator(realm string, secrets SecretProvider) *BasicAuth {
return &BasicAuth{Realm: realm, Secrets: secrets}
}

func NewBasicAuthenticatorForProxy(realm string, secrets SecretProvider) *BasicAuth {
return &BasicAuth{IsProxy: true, Realm: realm, Secrets: secrets}
}
55 changes: 29 additions & 26 deletions basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,36 @@ import (

func TestAuthBasic(t *testing.T) {
secrets := HtpasswdFileProvider("test.htpasswd")
a := &BasicAuth{Realm: "example.com", Secrets: secrets}
r := &http.Request{}
r.Method = "GET"
if a.CheckAuth(r) != "" {
t.Fatal("CheckAuth passed on empty headers")
}
r.Header = http.Header(make(map[string][]string))
r.Header.Set("Authorization", "Digest blabla ololo")
if a.CheckAuth(r) != "" {
t.Fatal("CheckAuth passed on bad headers")
}
r.Header.Set("Authorization", "Basic !@#")
if a.CheckAuth(r) != "" {
t.Fatal("CheckAuth passed on bad base64 data")
}

data := [][]string{
{"test", "hello"},
{"test2", "hello2"},
{"test3", "hello3"},
{"test16", "topsecret"},
}
for _, tc := range data {
auth := base64.StdEncoding.EncodeToString([]byte(tc[0] + ":" + tc[1]))
r.Header.Set("Authorization", "Basic "+auth)
if a.CheckAuth(r) != tc[0] {
t.Fatalf("CheckAuth failed for user '%s'", tc[0])
for _, isProxy := range []bool{false, true} {
a := &BasicAuth{IsProxy: isProxy, Realm: "example.com", Secrets: secrets}
r := &http.Request{}
r.Method = "GET"
if a.CheckAuth(r) != "" {
t.Fatal("CheckAuth passed on empty headers")
}
r.Header = http.Header(make(map[string][]string))
r.Header.Set(AuthorizationHeaderName(a.IsProxy), "Digest blabla ololo")
if a.CheckAuth(r) != "" {
t.Fatal("CheckAuth passed on bad headers")
}
r.Header.Set(AuthorizationHeaderName(a.IsProxy), "Basic !@#")
if a.CheckAuth(r) != "" {
t.Fatal("CheckAuth passed on bad base64 data")
}

data := [][]string{
{"test", "hello"},
{"test2", "hello2"},
{"test3", "hello3"},
{"test16", "topsecret"},
}
for _, tc := range data {
auth := base64.StdEncoding.EncodeToString([]byte(tc[0] + ":" + tc[1]))
r.Header.Set(AuthorizationHeaderName(a.IsProxy), "Basic "+auth)
if a.CheckAuth(r) != tc[0] {
t.Fatalf("CheckAuth failed for user '%s'", tc[0])
}
}
}
}
72 changes: 72 additions & 0 deletions bitset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Package bitset implments a memory efficient bit array of booleans
// Adapted from https://github.com/lazybeaver/bitset

package auth

import "fmt"

type BitSet struct {
bits []uint8
size uint64
}

const (
bitMaskZero = uint8(0)
bitMaskOnes = uint8((1 << 8) - 1)
)

var (
bitMasks = [...]uint8{0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}
)

func (b *BitSet) getPositionAndMask(index uint64) (uint64, uint8) {
if index < 0 || index >= b.size {
panic(fmt.Errorf("BitSet index (%d) out of bounds (size: %d)", index, b.size))
}
position := index >> 3
mask := bitMasks[index%8]
return position, mask
}

func (b *BitSet) Init(size uint64) {
b.bits = make([]uint8, (size+7)/8)
b.size = size
}

func (b *BitSet) Size() uint64 {
return b.size
}

func (b *BitSet) Get(index uint64) bool {
position, mask := b.getPositionAndMask(index)
return (b.bits[position] & mask) != 0
}

func (b *BitSet) Set(index uint64) {
position, mask := b.getPositionAndMask(index)
b.bits[position] |= mask
}

func (b *BitSet) Clear(index uint64) {
position, mask := b.getPositionAndMask(index)
b.bits[position] &^= mask
}

func (b *BitSet) String() string {
value := make([]byte, b.size)
var i uint64
for i = 0; i < b.size; i++ {
if b.Get(i) {
value[i] = '1'
} else {
value[i] = '0'
}
}
return string(value)
}

func NewBitSet(size uint64) *BitSet {
b := &BitSet{}
b.Init(size)
return b
}
79 changes: 79 additions & 0 deletions bitset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package auth

import (
"testing"
)

func TestNew(t *testing.T) {
var size uint64 = 101
bs := NewBitSet(size)
if bs.Size() != size {
t.Errorf("Unexpected initialization failure")
}
var i uint64
for i = 0; i < size; i++ {
if bs.Get(i) {
t.Errorf("Newly initialized bitset cannot have true values")
}
}
}

func TestGet(t *testing.T) {
bs := NewBitSet(2)
bs.Set(0)
bs.Clear(1)
if bs.Get(0) != true {
t.Errorf("Actual: false | Expected: true")
}
if bs.Get(1) != false {
t.Errorf("Actual: true | Expected: false")
}
}

func TestSet(t *testing.T) {
bs := NewBitSet(10)
bs.Set(2)
bs.Set(3)
bs.Set(5)
bs.Set(7)
actual := bs.String()
expected := "0011010100"
if actual != expected {
t.Errorf("Actual: %s | Expected: %s", actual, expected)
}
}

func TestClear(t *testing.T) {
bs := NewBitSet(10)
var i uint64
for i = 0; i < 10; i++ {
bs.Set(i)
}
bs.Clear(0)
bs.Clear(3)
bs.Clear(6)
bs.Clear(9)
actual := bs.String()
expected := "0110110110"
if actual != expected {
t.Errorf("Actual: %s | Expected: %s", actual, expected)
}
}

func BenchmarkGet(b *testing.B) {
bn := uint64(b.N)
bs := NewBitSet(bn)
var i uint64
for i = 0; i < bn; i++ {
_ = bs.Get(i)
}
}

func BenchmarkSet(b *testing.B) {
bn := uint64(b.N)
bs := NewBitSet(bn)
var i uint64
for i = 0; i < bn; i++ {
bs.Set(i)
}
}
Loading