Skip to content
Closed
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 client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Client interface {

Compare(dn, attribute, value string) (bool, error)
PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error)
WhoAmI(controls []Control) (*WhoAmIResult, error)

Search(searchRequest *SearchRequest) (*SearchResult, error)
SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
Expand Down
47 changes: 47 additions & 0 deletions control.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ const (
ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5"
// ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296
ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2"
// ControlTypeProxiedAuthorization - https://tools.ietf.org/html/rfc4370
ControlTypeProxiedAuthorization = "2.16.840.1.113730.3.4.18"
)

// ControlTypeMap maps controls to text descriptions
var ControlTypeMap = map[string]string{
ControlTypePaging: "Paging",
ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
ControlTypeManageDsaIT: "Manage DSA IT",
ControlTypeProxiedAuthorization: "Proxied Authorization",
}

// Control defines an interface controls provide to encode and describe themselves
Expand Down Expand Up @@ -238,6 +241,44 @@ func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT {
return &ControlManageDsaIT{Criticality: Criticality}
}

// ControlProxiedAuthorization implements the control described in
// https://tools.ietf.org/html/rfc4370
type ControlProxiedAuthorization struct {
Criticality bool
AuthzID string
}

// GetControlType returns the OID
func (c *ControlProxiedAuthorization) GetControlType() string {
return ControlTypeProxiedAuthorization
}

// Encode returns the ber packet representation
func (c *ControlProxiedAuthorization) Encode() *ber.Packet {
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeProxiedAuthorization, "Control Type ("+ControlTypeMap[ControlTypeProxiedAuthorization]+")"))
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.AuthzID, "AuthzID"))
return packet
}

// String returns a human-readable description
func (c *ControlProxiedAuthorization) String() string {
return fmt.Sprintf(
"Control Type: %s (%q) Criticality: %t",
ControlTypeMap[ControlTypeProxiedAuthorization],
ControlTypeProxiedAuthorization,
c.Criticality)
}

// NewControlProxiedAuthorization returns a ProxiedAuthorization control
func NewControlProxiedAuthorization(authzID string) *ControlProxiedAuthorization {
return &ControlProxiedAuthorization{
Criticality: true,
AuthzID: authzID,
}
}

// FindControl returns the first control of the given type in the list, or nil
func FindControl(controls []Control, controlType string) Control {
for _, c := range controls {
Expand Down Expand Up @@ -373,6 +414,12 @@ func DecodeControl(packet *ber.Packet) Control {
value.Value = c.Expire

return c

case ControlTypeProxiedAuthorization:
c := &ControlProxiedAuthorization{Criticality: true}
authzID := ber.DecodeString(value.Data.Bytes())
c.AuthzID = authzID
return c
default:
c := new(ControlString)
c.ControlType = ControlType
Expand Down
4 changes: 4 additions & 0 deletions control_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ func TestControlString(t *testing.T) {
runControlTest(t, NewControlString("x", false, ""))
}

func TestControlProxiedAuthorization(t *testing.T) {
runControlTest(t, NewControlProxiedAuthorization("dn:uid=someone,ou=people,dc=example,dc=net"))
}

func runControlTest(t *testing.T, originalControl Control) {
header := ""
if callerpc, _, line, ok := runtime.Caller(1); ok {
Expand Down
6 changes: 6 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ const (
LDAPResultObjectClassModsProhibited = 69
LDAPResultAffectsMultipleDSAs = 71
LDAPResultOther = 80
// https://tools.ietf.org/html/rfc4370 chap 6:
// "A result code (123) has been assigned by the IANA for the case where
// the server does not execute a request using the proxy authorization
// identity."
LDAPResultAuthorizationDenied = 123
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these are already in now from PR #160


ErrorNetwork = 200
ErrorFilterCompile = 201
Expand Down Expand Up @@ -97,6 +102,7 @@ var LDAPResultCodeMap = map[uint8]string{
LDAPResultEntryAlreadyExists: "Entry Already Exists",
LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited",
LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs",
LDAPResultAuthorizationDenied: "Authorization Denied",
LDAPResultOther: "Other",

ErrorNetwork: "Network Error",
Expand Down
97 changes: 97 additions & 0 deletions whoami.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// This file contains the "Who Am I?" extended operation as specified in rfc 4532
//
// https://tools.ietf.org/html/rfc4532
//

package ldap

import (
"errors"
"fmt"

"gopkg.in/asn1-ber.v1"
)

const (
whoamiOID = "1.3.6.1.4.1.4203.1.11.3"
)

type whoAmIRequest bool

// WhoAmIResult is returned by the WhoAmI() call
type WhoAmIResult struct {
AuthzID string
}

func (r whoAmIRequest) encode() (*ber.Packet, error) {
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Who Am I? Extended Operation")
request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, whoamiOID, "Extended Request Name: Who Am I? OID"))
return request, nil
}

// WhoAmI returns the authzId the server thinks we are, you may pass controls
// like a Proxied Authorization control
func (l *Conn) WhoAmI(controls []Control) (*WhoAmIResult, error) {
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, l.nextMessageID(), "MessageID"))
req := whoAmIRequest(true)
encodedWhoAmIRequest, err := req.encode()
if err != nil {
return nil, err
}
packet.AppendChild(encodedWhoAmIRequest)

if len(controls) != 0 {
packet.AppendChild(encodeControls(controls))
}

l.Debug.PrintPacket(packet)

msgCtx, err := l.sendMessage(packet)
if err != nil {
return nil, err
}
defer l.finishMessage(msgCtx)

result := &WhoAmIResult{}

l.Debug.Printf("%d: waiting for response", msgCtx.id)
packetResponse, ok := <-msgCtx.responses
if !ok {
return nil, NewError(ErrorNetwork, errors.New("ldap: response channel closed"))
}
packet, err = packetResponse.ReadPacket()
l.Debug.Printf("%d: got response %p", msgCtx.id, packet)
if err != nil {
return nil, err
}

if packet == nil {
return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
}

if l.Debug {
if err := addLDAPDescriptions(packet); err != nil {
return nil, err
}
ber.PrintPacket(packet)
}

if packet.Children[1].Tag == ApplicationExtendedResponse {
resultCode, resultDescription := getLDAPResultCode(packet)
if resultCode != 0 {
return nil, NewError(resultCode, errors.New(resultDescription))
}
} else {
return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag))
}

extendedResponse := packet.Children[1]
for _, child := range extendedResponse.Children {
if child.Tag == 11 {
result.AuthzID = ber.DecodeString(child.Data.Bytes())
}
}

return result, nil
}
52 changes: 52 additions & 0 deletions whoami_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package ldap_test

import (
"fmt"
"log"

"gopkg.in/ldap.v2"
)

func ExampleWhoAmI() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there tests for WhoAmI possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a encode / decode test to control_test.go

conn, err := ldap.Dial("tcp", "ldap.example.org:389")
if err != nil {
log.Fatalf("Failed to connect: %s\n", err)
}

_, err = conn.SimpleBind(&ldap.SimpleBindRequest{
Username: "uid=someone,ou=people,dc=example,dc=org",
Password: "MySecretPass",
})
if err != nil {
log.Fatalf("Failed to bind: %s\n", err)
}

res, err := conn.WhoAmI(nil)
if err != nil {
log.Fatalf("Failed to call WhoAmI(): %s\n", err)
}
fmt.Printf("I am: %s\n", res.AuthzID)
}

func ExampleWhoAmIProxied() {
conn, err := ldap.Dial("tcp", "ldap.example.org:389")
if err != nil {
log.Fatalf("Failed to connect: %s\n", err)
}

_, err = conn.SimpleBind(&ldap.SimpleBindRequest{
Username: "uid=someone,ou=people,dc=example,dc=org",
Password: "MySecretPass",
})
if err != nil {
log.Fatalf("Failed to bind: %s\n", err)
}

pa := ldap.NewControlProxiedAuthorization("dn:uid=other,ou=people,dc=example,dc=org")

res, err := conn.WhoAmI([]ldap.Control{pa})
if err != nil {
log.Fatalf("Failed to call WhoAmI(): %s\n", err)
}
fmt.Printf("For this call only I am now: %s\n", res.AuthzID)
}