summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Godeps/Godeps.json10
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/.gitignore0
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml15
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/LICENSE27
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/README.md55
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/add.go104
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/bind.go135
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/client.go23
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/compare.go85
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go369
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/control.go332
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/debug.go24
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/del.go79
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go155
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go70
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/doc.go4
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/error.go137
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go305
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go456
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go248
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go286
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go249
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/modify.go156
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/passwdmodify.go137
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/search.go403
-rw-r--r--Godeps/_workspace/src/github.com/go-ldap/ldap/search_test.go31
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml15
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE27
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md24
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go504
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber_test.go168
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/content_int.go25
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header.go29
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header_test.go135
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier.go103
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier_test.go344
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length.go71
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length_test.go158
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/suite_test.go182
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc1.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc10.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc11.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc12.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc13.berbin0 -> 11 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc14.berbin0 -> 7 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc15.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc16.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc17.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc18.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc19.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc2.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc20.berbin0 -> 11 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc21.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc22.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc23.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc24.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc25.berbin0 -> 5 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc26.berbin0 -> 5 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc27.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc28.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc29.berbin0 -> 3 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc3.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc30.berbin0 -> 5 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc31.berbin0 -> 4 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc32.berbin0 -> 2 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc33.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc34.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc35.berbin0 -> 16 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc36.berbin0 -> 20 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc37.berbin0 -> 14 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc38.berbin0 -> 16 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc39.berbin0 -> 2 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc4.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc40.berbin0 -> 2 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc41.berbin0 -> 16 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc42.berbin0 -> 14 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc43.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc44.berbin0 -> 2 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc45.berbin0 -> 2 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc46.berbin0 -> 11 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc47.berbin0 -> 16 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc48.berbin0 -> 16 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc5.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc6.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc7.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc8.berbin0 -> 5 bytes
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc9.ber1
-rw-r--r--Godeps/_workspace/src/gopkg.in/asn1-ber.v1/util.go24
-rw-r--r--Makefile31
-rw-r--r--api/team.go5
-rw-r--r--api/user.go100
-rw-r--r--einterfaces/ldap.go22
-rw-r--r--einterfaces/oauthproviders.go29
-rw-r--r--mattermost.go23
-rw-r--r--model/config.go43
-rw-r--r--model/gitlab/gitlab.go (renamed from model/gitlab.go)34
-rw-r--r--model/version.go1
-rw-r--r--store/sql_user_store.go2
-rw-r--r--utils/config.go4
-rw-r--r--utils/diagnostic.go1
-rw-r--r--web/react/components/about_build_modal.jsx8
-rw-r--r--web/react/components/admin_console/admin_controller.jsx3
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx11
-rw-r--r--web/react/components/admin_console/ldap_settings.jsx388
-rw-r--r--web/react/components/login.jsx146
-rw-r--r--web/react/components/login_email.jsx137
-rw-r--r--web/react/components/login_ldap.jsx110
-rw-r--r--web/react/components/signup_team.jsx7
-rw-r--r--web/react/components/signup_user_complete.jsx14
-rw-r--r--web/react/components/team_signup_choose_auth.jsx18
-rw-r--r--web/react/components/team_signup_with_sso.jsx12
-rw-r--r--web/react/utils/client.jsx22
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/web.go30
114 files changed, 6761 insertions, 170 deletions
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 52b7d5e92..bd2392f90 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -30,6 +30,11 @@
"Rev": "c0e2e1a7adaa00e47c4f6ae8faaad6991a4570ac"
},
{
+ "ImportPath": "github.com/go-ldap/ldap",
+ "Comment": "v2.2",
+ "Rev": "e9a325d64989e2844be629682cb085d2c58eef8d"
+ },
+ {
"ImportPath": "github.com/go-sql-driver/mysql",
"Comment": "v1.2-125-gd512f20",
"Rev": "d512f204a577a4ab037a1816604c48c9c13210be"
@@ -114,6 +119,11 @@
"Rev": "baddd3465a05d84a6d8d3507547a91cb188c81ea"
},
{
+ "ImportPath": "gopkg.in/asn1-ber.v1",
+ "Comment": "v1.1",
+ "Rev": "4e86f4367175e39f69d9358a5f17b4dda270378d"
+ },
+ {
"ImportPath": "gopkg.in/fsnotify.v1",
"Comment": "v1.2.5",
"Rev": "2cdd39bd6129c6a49c74fb07fb9d77ba1271c572"
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/.gitignore b/Godeps/_workspace/src/github.com/go-ldap/ldap/.gitignore
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/.gitignore
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml b/Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml
new file mode 100644
index 000000000..a7a38951b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml
@@ -0,0 +1,15 @@
+language: go
+go:
+ - 1.2
+ - 1.3
+ - 1.4
+ - 1.5
+ - tip
+go_import_path: gopkg.in/ldap.v2
+install:
+ - go get gopkg.in/asn1-ber.v1
+ - go get gopkg.in/ldap.v2
+ - go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
+ - go build -v ./...
+script:
+ - go test -v -cover ./...
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/LICENSE b/Godeps/_workspace/src/github.com/go-ldap/ldap/LICENSE
new file mode 100644
index 000000000..744875676
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/README.md b/Godeps/_workspace/src/github.com/go-ldap/ldap/README.md
new file mode 100644
index 000000000..68121c3e2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/README.md
@@ -0,0 +1,55 @@
+[![GoDoc](https://godoc.org/gopkg.in/ldap.v1?status.svg)](https://godoc.org/gopkg.in/ldap.v1)
+[![Build Status](https://travis-ci.org/go-ldap/ldap.svg)](https://travis-ci.org/go-ldap/ldap)
+
+# Basic LDAP v3 functionality for the GO programming language.
+
+## Install
+
+For the latest version use:
+
+ go get gopkg.in/ldap.v2
+
+Import the latest version with:
+
+ import "gopkg.in/ldap.v2"
+
+
+## Required Libraries:
+
+ - gopkg.in/asn1-ber.v1
+
+## Working:
+
+ - Connecting to LDAP server
+ - Binding to LDAP server
+ - Searching for entries
+ - Compiling string filters to LDAP filters
+ - Paging Search Results
+ - Modify Requests / Responses
+ - Add Requests / Responses
+ - Delete Requests / Responses
+ - Better Unicode support
+
+## Examples:
+
+ - search
+ - modify
+
+## Tests Implemented:
+
+ - Filter Compile / Decompile
+
+## TODO:
+
+ - [x] Add Requests / Responses
+ - [x] Delete Requests / Responses
+ - [x] Modify DN Requests / Responses
+ - [ ] Compare Requests / Responses
+ - [ ] Implement Tests / Benchmarks
+
+
+
+---
+The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
+The design is licensed under the Creative Commons 3.0 Attributions license.
+Read this article for more details: http://blog.golang.org/gopher
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/add.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/add.go
new file mode 100644
index 000000000..643ce5ffe
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/add.go
@@ -0,0 +1,104 @@
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// AddRequest ::= [APPLICATION 8] SEQUENCE {
+// entry LDAPDN,
+// attributes AttributeList }
+//
+// AttributeList ::= SEQUENCE OF attribute Attribute
+
+package ldap
+
+import (
+ "errors"
+ "log"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+type Attribute struct {
+ attrType string
+ attrVals []string
+}
+
+func (a *Attribute) encode() *ber.Packet {
+ seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attribute")
+ seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.attrType, "Type"))
+ set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
+ for _, value := range a.attrVals {
+ set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
+ }
+ seq.AppendChild(set)
+ return seq
+}
+
+type AddRequest struct {
+ dn string
+ attributes []Attribute
+}
+
+func (a AddRequest) encode() *ber.Packet {
+ request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationAddRequest, nil, "Add Request")
+ request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, a.dn, "DN"))
+ attributes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
+ for _, attribute := range a.attributes {
+ attributes.AppendChild(attribute.encode())
+ }
+ request.AppendChild(attributes)
+ return request
+}
+
+func (a *AddRequest) Attribute(attrType string, attrVals []string) {
+ a.attributes = append(a.attributes, Attribute{attrType: attrType, attrVals: attrVals})
+}
+
+func NewAddRequest(dn string) *AddRequest {
+ return &AddRequest{
+ dn: dn,
+ }
+
+}
+
+func (l *Conn) Add(addRequest *AddRequest) error {
+ messageID := l.nextMessageID()
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+ packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+ packet.AppendChild(addRequest.encode())
+
+ l.Debug.PrintPacket(packet)
+
+ channel, err := l.sendMessage(packet)
+ if err != nil {
+ return err
+ }
+ if channel == nil {
+ return NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+ }
+ defer l.finishMessage(messageID)
+
+ l.Debug.Printf("%d: waiting for response", messageID)
+ packet = <-channel
+ l.Debug.Printf("%d: got response %p", messageID, packet)
+ if packet == nil {
+ return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
+ }
+
+ if l.Debug {
+ if err := addLDAPDescriptions(packet); err != nil {
+ return err
+ }
+ ber.PrintPacket(packet)
+ }
+
+ if packet.Children[1].Tag == ApplicationAddResponse {
+ resultCode, resultDescription := getLDAPResultCode(packet)
+ if resultCode != 0 {
+ return NewError(resultCode, errors.New(resultDescription))
+ }
+ } else {
+ log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+ }
+
+ l.Debug.Printf("%d: returning", messageID)
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/bind.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/bind.go
new file mode 100644
index 000000000..4ad4b896c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/bind.go
@@ -0,0 +1,135 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ldap
+
+import (
+ "errors"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+type SimpleBindRequest struct {
+ Username string
+ Password string
+ Controls []Control
+}
+
+type SimpleBindResult struct {
+ Controls []Control
+}
+
+func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest {
+ return &SimpleBindRequest{
+ Username: username,
+ Password: password,
+ Controls: controls,
+ }
+}
+
+func (bindRequest *SimpleBindRequest) encode() *ber.Packet {
+ request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
+ request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
+ request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, bindRequest.Username, "User Name"))
+ request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, bindRequest.Password, "Password"))
+
+ request.AppendChild(encodeControls(bindRequest.Controls))
+
+ return request
+}
+
+func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) {
+ messageID := l.nextMessageID()
+
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+ packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+ encodedBindRequest := simpleBindRequest.encode()
+ packet.AppendChild(encodedBindRequest)
+
+ if l.Debug {
+ ber.PrintPacket(packet)
+ }
+
+ channel, err := l.sendMessage(packet)
+ if err != nil {
+ return nil, err
+ }
+ if channel == nil {
+ return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+ }
+ defer l.finishMessage(messageID)
+
+ packet = <-channel
+ if packet == nil {
+ return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve response"))
+ }
+
+ if l.Debug {
+ if err := addLDAPDescriptions(packet); err != nil {
+ return nil, err
+ }
+ ber.PrintPacket(packet)
+ }
+
+ result := &SimpleBindResult{
+ Controls: make([]Control, 0),
+ }
+
+ if len(packet.Children) == 3 {
+ for _, child := range packet.Children[2].Children {
+ result.Controls = append(result.Controls, DecodeControl(child))
+ }
+ }
+
+ resultCode, resultDescription := getLDAPResultCode(packet)
+ if resultCode != 0 {
+ return result, NewError(resultCode, errors.New(resultDescription))
+ }
+
+ return result, nil
+}
+
+func (l *Conn) Bind(username, password string) error {
+ messageID := l.nextMessageID()
+
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+ packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+ bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
+ bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
+ bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, username, "User Name"))
+ bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, password, "Password"))
+ packet.AppendChild(bindRequest)
+
+ if l.Debug {
+ ber.PrintPacket(packet)
+ }
+
+ channel, err := l.sendMessage(packet)
+ if err != nil {
+ return err
+ }
+ if channel == nil {
+ return NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+ }
+ defer l.finishMessage(messageID)
+
+ packet = <-channel
+ if packet == nil {
+ return NewError(ErrorNetwork, errors.New("ldap: could not retrieve response"))
+ }
+
+ if l.Debug {
+ if err := addLDAPDescriptions(packet); err != nil {
+ return err
+ }
+ ber.PrintPacket(packet)
+ }
+
+ resultCode, resultDescription := getLDAPResultCode(packet)
+ if resultCode != 0 {
+ return NewError(resultCode, errors.New(resultDescription))
+ }
+
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/client.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/client.go
new file mode 100644
index 000000000..d3401f9e6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/client.go
@@ -0,0 +1,23 @@
+package ldap
+
+import "crypto/tls"
+
+// Client knows how to interact with an LDAP server
+type Client interface {
+ Start()
+ StartTLS(config *tls.Config) error
+ Close()
+
+ Bind(username, password string) error
+ SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error)
+
+ Add(addRequest *AddRequest) error
+ Del(delRequest *DelRequest) error
+ Modify(modifyRequest *ModifyRequest) error
+
+ Compare(dn, attribute, value string) (bool, error)
+ PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error)
+
+ Search(searchRequest *SearchRequest) (*SearchResult, error)
+ SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error)
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/compare.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/compare.go
new file mode 100644
index 000000000..802e9cc93
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/compare.go
@@ -0,0 +1,85 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+// File contains Compare functionality
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// CompareRequest ::= [APPLICATION 14] SEQUENCE {
+// entry LDAPDN,
+// ava AttributeValueAssertion }
+//
+// AttributeValueAssertion ::= SEQUENCE {
+// attributeDesc AttributeDescription,
+// assertionValue AssertionValue }
+//
+// AttributeDescription ::= LDAPString
+// -- Constrained to <attributedescription>
+// -- [RFC4512]
+//
+// AttributeValue ::= OCTET STRING
+//
+
+package ldap
+
+import (
+ "errors"
+ "fmt"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+// Compare checks to see if the attribute of the dn matches value. Returns true if it does otherwise
+// false with any error that occurs if any.
+func (l *Conn) Compare(dn, attribute, value string) (bool, error) {
+ messageID := l.nextMessageID()
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+ packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+
+ request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationCompareRequest, nil, "Compare Request")
+ request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, dn, "DN"))
+
+ ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion")
+ ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "AttributeDesc"))
+ ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagOctetString, value, "AssertionValue"))
+ request.AppendChild(ava)
+ packet.AppendChild(request)
+
+ l.Debug.PrintPacket(packet)
+
+ channel, err := l.sendMessage(packet)
+ if err != nil {
+ return false, err
+ }
+ if channel == nil {
+ return false, NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+ }
+ defer l.finishMessage(messageID)
+
+ l.Debug.Printf("%d: waiting for response", messageID)
+ packet = <-channel
+ l.Debug.Printf("%d: got response %p", messageID, packet)
+ if packet == nil {
+ return false, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
+ }
+
+ if l.Debug {
+ if err := addLDAPDescriptions(packet); err != nil {
+ return false, err
+ }
+ ber.PrintPacket(packet)
+ }
+
+ if packet.Children[1].Tag == ApplicationCompareResponse {
+ resultCode, resultDescription := getLDAPResultCode(packet)
+ if resultCode == LDAPResultCompareTrue {
+ return true, nil
+ } else if resultCode == LDAPResultCompareFalse {
+ return false, nil
+ } else {
+ return false, NewError(resultCode, errors.New(resultDescription))
+ }
+ }
+ return false, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag)
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go
new file mode 100644
index 000000000..2f16443f6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go
@@ -0,0 +1,369 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ldap
+
+import (
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "log"
+ "net"
+ "sync"
+ "time"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+const (
+ MessageQuit = 0
+ MessageRequest = 1
+ MessageResponse = 2
+ MessageFinish = 3
+)
+
+type messagePacket struct {
+ Op int
+ MessageID int64
+ Packet *ber.Packet
+ Channel chan *ber.Packet
+}
+
+type sendMessageFlags uint
+
+const (
+ startTLS sendMessageFlags = 1 << iota
+)
+
+// Conn represents an LDAP Connection
+type Conn struct {
+ conn net.Conn
+ isTLS bool
+ isClosing bool
+ isStartingTLS bool
+ Debug debugging
+ chanConfirm chan bool
+ chanResults map[int64]chan *ber.Packet
+ chanMessage chan *messagePacket
+ chanMessageID chan int64
+ wgSender sync.WaitGroup
+ wgClose sync.WaitGroup
+ once sync.Once
+ outstandingRequests uint
+ messageMutex sync.Mutex
+}
+
+var _ Client = &Conn{}
+
+// DefaultTimeout is a package-level variable that sets the timeout value
+// used for the Dial and DialTLS methods.
+//
+// WARNING: since this is a package-level variable, setting this value from
+// multiple places will probably result in undesired behaviour.
+var DefaultTimeout = 60 * time.Second
+
+// Dial connects to the given address on the given network using net.Dial
+// and then returns a new Conn for the connection.
+func Dial(network, addr string) (*Conn, error) {
+ c, err := net.DialTimeout(network, addr, DefaultTimeout)
+ if err != nil {
+ return nil, NewError(ErrorNetwork, err)
+ }
+ conn := NewConn(c, false)
+ conn.Start()
+ return conn, nil
+}
+
+// DialTLS connects to the given address on the given network using tls.Dial
+// and then returns a new Conn for the connection.
+func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
+ dc, err := net.DialTimeout(network, addr, DefaultTimeout)
+ if err != nil {
+ return nil, NewError(ErrorNetwork, err)
+ }
+ c := tls.Client(dc, config)
+ err = c.Handshake()
+ if err != nil {
+ // Handshake error, close the established connection before we return an error
+ dc.Close()
+ return nil, NewError(ErrorNetwork, err)
+ }
+ conn := NewConn(c, true)
+ conn.Start()
+ return conn, nil
+}
+
+// NewConn returns a new Conn using conn for network I/O.
+func NewConn(conn net.Conn, isTLS bool) *Conn {
+ return &Conn{
+ conn: conn,
+ chanConfirm: make(chan bool),
+ chanMessageID: make(chan int64),
+ chanMessage: make(chan *messagePacket, 10),
+ chanResults: map[int64]chan *ber.Packet{},
+ isTLS: isTLS,
+ }
+}
+
+func (l *Conn) Start() {
+ go l.reader()
+ go l.processMessages()
+ l.wgClose.Add(1)
+}
+
+// Close closes the connection.
+func (l *Conn) Close() {
+ l.once.Do(func() {
+ l.isClosing = true
+ l.wgSender.Wait()
+
+ l.Debug.Printf("Sending quit message and waiting for confirmation")
+ l.chanMessage <- &messagePacket{Op: MessageQuit}
+ <-l.chanConfirm
+ close(l.chanMessage)
+
+ l.Debug.Printf("Closing network connection")
+ if err := l.conn.Close(); err != nil {
+ log.Print(err)
+ }
+
+ l.wgClose.Done()
+ })
+ l.wgClose.Wait()
+}
+
+// Returns the next available messageID
+func (l *Conn) nextMessageID() int64 {
+ if l.chanMessageID != nil {
+ if messageID, ok := <-l.chanMessageID; ok {
+ return messageID
+ }
+ }
+ return 0
+}
+
+// StartTLS sends the command to start a TLS session and then creates a new TLS Client
+func (l *Conn) StartTLS(config *tls.Config) error {
+ messageID := l.nextMessageID()
+
+ if l.isTLS {
+ return NewError(ErrorNetwork, errors.New("ldap: already encrypted"))
+ }
+
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+ packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+ request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS")
+ request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command"))
+ packet.AppendChild(request)
+ l.Debug.PrintPacket(packet)
+
+ channel, err := l.sendMessageWithFlags(packet, startTLS)
+ if err != nil {
+ return err
+ }
+ if channel == nil {
+ return NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+ }
+
+ l.Debug.Printf("%d: waiting for response", messageID)
+ packet = <-channel
+ l.Debug.Printf("%d: got response %p", messageID, packet)
+ l.finishMessage(messageID)
+
+ if l.Debug {
+ if err := addLDAPDescriptions(packet); err != nil {
+ l.Close()
+ return err
+ }
+ ber.PrintPacket(packet)
+ }
+
+ if resultCode, message := getLDAPResultCode(packet); resultCode == LDAPResultSuccess {
+ conn := tls.Client(l.conn, config)
+
+ if err := conn.Handshake(); err != nil {
+ l.Close()
+ return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", err))
+ }
+
+ l.isTLS = true
+ l.conn = conn
+ } else {
+ return NewError(resultCode, fmt.Errorf("ldap: cannot StartTLS (%s)", message))
+ }
+ go l.reader()
+
+ return nil
+}
+
+func (l *Conn) sendMessage(packet *ber.Packet) (chan *ber.Packet, error) {
+ return l.sendMessageWithFlags(packet, 0)
+}
+
+func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (chan *ber.Packet, error) {
+ if l.isClosing {
+ return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed"))
+ }
+ l.messageMutex.Lock()
+ l.Debug.Printf("flags&startTLS = %d", flags&startTLS)
+ if l.isStartingTLS {
+ l.messageMutex.Unlock()
+ return nil, NewError(ErrorNetwork, errors.New("ldap: connection is in startls phase."))
+ }
+ if flags&startTLS != 0 {
+ if l.outstandingRequests != 0 {
+ l.messageMutex.Unlock()
+ return nil, NewError(ErrorNetwork, errors.New("ldap: cannot StartTLS with outstanding requests"))
+ } else {
+ l.isStartingTLS = true
+ }
+ }
+ l.outstandingRequests++
+
+ l.messageMutex.Unlock()
+
+ out := make(chan *ber.Packet)
+ message := &messagePacket{
+ Op: MessageRequest,
+ MessageID: packet.Children[0].Value.(int64),
+ Packet: packet,
+ Channel: out,
+ }
+ l.sendProcessMessage(message)
+ return out, nil
+}
+
+func (l *Conn) finishMessage(messageID int64) {
+ if l.isClosing {
+ return
+ }
+
+ l.messageMutex.Lock()
+ l.outstandingRequests--
+ if l.isStartingTLS {
+ l.isStartingTLS = false
+ }
+ l.messageMutex.Unlock()
+
+ message := &messagePacket{
+ Op: MessageFinish,
+ MessageID: messageID,
+ }
+ l.sendProcessMessage(message)
+}
+
+func (l *Conn) sendProcessMessage(message *messagePacket) bool {
+ if l.isClosing {
+ return false
+ }
+ l.wgSender.Add(1)
+ l.chanMessage <- message
+ l.wgSender.Done()
+ return true
+}
+
+func (l *Conn) processMessages() {
+ defer func() {
+ if err := recover(); err != nil {
+ log.Printf("ldap: recovered panic in processMessages: %v", err)
+ }
+ for messageID, channel := range l.chanResults {
+ l.Debug.Printf("Closing channel for MessageID %d", messageID)
+ close(channel)
+ delete(l.chanResults, messageID)
+ }
+ close(l.chanMessageID)
+ l.chanConfirm <- true
+ close(l.chanConfirm)
+ }()
+
+ var messageID int64 = 1
+ for {
+ select {
+ case l.chanMessageID <- messageID:
+ messageID++
+ case messagePacket, ok := <-l.chanMessage:
+ if !ok {
+ l.Debug.Printf("Shutting down - message channel is closed")
+ return
+ }
+ switch messagePacket.Op {
+ case MessageQuit:
+ l.Debug.Printf("Shutting down - quit message received")
+ return
+ case MessageRequest:
+ // Add to message list and write to network
+ l.Debug.Printf("Sending message %d", messagePacket.MessageID)
+ l.chanResults[messagePacket.MessageID] = messagePacket.Channel
+ // go routine
+ buf := messagePacket.Packet.Bytes()
+
+ _, err := l.conn.Write(buf)
+ if err != nil {
+ l.Debug.Printf("Error Sending Message: %s", err.Error())
+ break
+ }
+ case MessageResponse:
+ l.Debug.Printf("Receiving message %d", messagePacket.MessageID)
+ if chanResult, ok := l.chanResults[messagePacket.MessageID]; ok {
+ chanResult <- messagePacket.Packet
+ } else {
+ log.Printf("Received unexpected message %d", messagePacket.MessageID)
+ ber.PrintPacket(messagePacket.Packet)
+ }
+ case MessageFinish:
+ // Remove from message list
+ l.Debug.Printf("Finished message %d", messagePacket.MessageID)
+ close(l.chanResults[messagePacket.MessageID])
+ delete(l.chanResults, messagePacket.MessageID)
+ }
+ }
+ }
+}
+
+func (l *Conn) reader() {
+ cleanstop := false
+ defer func() {
+ if err := recover(); err != nil {
+ log.Printf("ldap: recovered panic in reader: %v", err)
+ }
+ if !cleanstop {
+ l.Close()
+ }
+ }()
+
+ for {
+ if cleanstop {
+ l.Debug.Printf("reader clean stopping (without closing the connection)")
+ return
+ }
+ packet, err := ber.ReadPacket(l.conn)
+ if err != nil {
+ // A read error is expected here if we are closing the connection...
+ if !l.isClosing {
+ l.Debug.Printf("reader error: %s", err.Error())
+ }
+ return
+ }
+ addLDAPDescriptions(packet)
+ if len(packet.Children) == 0 {
+ l.Debug.Printf("Received bad ldap packet")
+ continue
+ }
+ l.messageMutex.Lock()
+ if l.isStartingTLS {
+ cleanstop = true
+ }
+ l.messageMutex.Unlock()
+ message := &messagePacket{
+ Op: MessageResponse,
+ MessageID: packet.Children[0].Value.(int64),
+ Packet: packet,
+ }
+ if !l.sendProcessMessage(message) {
+ return
+ }
+
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/control.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/control.go
new file mode 100644
index 000000000..4d8298093
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/control.go
@@ -0,0 +1,332 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ldap
+
+import (
+ "fmt"
+ "strconv"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+const (
+ ControlTypePaging = "1.2.840.113556.1.4.319"
+ ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1"
+ ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4"
+ ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5"
+ ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2"
+)
+
+var ControlTypeMap = map[string]string{
+ ControlTypePaging: "Paging",
+ ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft",
+ ControlTypeManageDsaIT: "Manage DSA IT",
+}
+
+type Control interface {
+ GetControlType() string
+ Encode() *ber.Packet
+ String() string
+}
+
+type ControlString struct {
+ ControlType string
+ Criticality bool
+ ControlValue string
+}
+
+func (c *ControlString) GetControlType() string {
+ return c.ControlType
+}
+
+func (c *ControlString) Encode() *ber.Packet {
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlType, "Control Type ("+ControlTypeMap[c.ControlType]+")"))
+ if c.Criticality {
+ packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
+ }
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(c.ControlValue), "Control Value"))
+ return packet
+}
+
+func (c *ControlString) String() string {
+ return fmt.Sprintf("Control Type: %s (%q) Criticality: %t Control Value: %s", ControlTypeMap[c.ControlType], c.ControlType, c.Criticality, c.ControlValue)
+}
+
+type ControlPaging struct {
+ PagingSize uint32
+ Cookie []byte
+}
+
+func (c *ControlPaging) GetControlType() string {
+ return ControlTypePaging
+}
+
+func (c *ControlPaging) Encode() *ber.Packet {
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypePaging, "Control Type ("+ControlTypeMap[ControlTypePaging]+")"))
+
+ p2 := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Paging)")
+ seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Search Control Value")
+ seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(c.PagingSize), "Paging Size"))
+ cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie")
+ cookie.Value = c.Cookie
+ cookie.Data.Write(c.Cookie)
+ seq.AppendChild(cookie)
+ p2.AppendChild(seq)
+
+ packet.AppendChild(p2)
+ return packet
+}
+
+func (c *ControlPaging) String() string {
+ return fmt.Sprintf(
+ "Control Type: %s (%q) Criticality: %t PagingSize: %d Cookie: %q",
+ ControlTypeMap[ControlTypePaging],
+ ControlTypePaging,
+ false,
+ c.PagingSize,
+ c.Cookie)
+}
+
+func (c *ControlPaging) SetCookie(cookie []byte) {
+ c.Cookie = cookie
+}
+
+type ControlBeheraPasswordPolicy struct {
+ Expire int64
+ Grace int64
+ Error int8
+ ErrorString string
+}
+
+func (c *ControlBeheraPasswordPolicy) GetControlType() string {
+ return ControlTypeBeheraPasswordPolicy
+}
+
+func (c *ControlBeheraPasswordPolicy) Encode() *ber.Packet {
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeBeheraPasswordPolicy, "Control Type ("+ControlTypeMap[ControlTypeBeheraPasswordPolicy]+")"))
+
+ return packet
+}
+
+func (c *ControlBeheraPasswordPolicy) String() string {
+ return fmt.Sprintf(
+ "Control Type: %s (%q) Criticality: %t Expire: %d Grace: %d Error: %d, ErrorString: %s",
+ ControlTypeMap[ControlTypeBeheraPasswordPolicy],
+ ControlTypeBeheraPasswordPolicy,
+ false,
+ c.Expire,
+ c.Grace,
+ c.Error,
+ c.ErrorString)
+}
+
+type ControlVChuPasswordMustChange struct {
+ MustChange bool
+}
+
+func (c *ControlVChuPasswordMustChange) GetControlType() string {
+ return ControlTypeVChuPasswordMustChange
+}
+
+func (c *ControlVChuPasswordMustChange) Encode() *ber.Packet {
+ return nil
+}
+
+func (c *ControlVChuPasswordMustChange) String() string {
+ return fmt.Sprintf(
+ "Control Type: %s (%q) Criticality: %t MustChange: %b",
+ ControlTypeMap[ControlTypeVChuPasswordMustChange],
+ ControlTypeVChuPasswordMustChange,
+ false,
+ c.MustChange)
+}
+
+type ControlVChuPasswordWarning struct {
+ Expire int64
+}
+
+func (c *ControlVChuPasswordWarning) GetControlType() string {
+ return ControlTypeVChuPasswordWarning
+}
+
+func (c *ControlVChuPasswordWarning) Encode() *ber.Packet {
+ return nil
+}
+
+func (c *ControlVChuPasswordWarning) String() string {
+ return fmt.Sprintf(
+ "Control Type: %s (%q) Criticality: %t Expire: %b",
+ ControlTypeMap[ControlTypeVChuPasswordWarning],
+ ControlTypeVChuPasswordWarning,
+ false,
+ c.Expire)
+}
+
+type ControlManageDsaIT struct {
+ Criticality bool
+}
+
+func (c *ControlManageDsaIT) GetControlType() string {
+ return ControlTypeManageDsaIT
+}
+
+func (c *ControlManageDsaIT) Encode() *ber.Packet {
+ //FIXME
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeManageDsaIT, "Control Type ("+ControlTypeMap[ControlTypeManageDsaIT]+")"))
+ if c.Criticality {
+ packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
+ }
+ return packet
+}
+
+func (c *ControlManageDsaIT) String() string {
+ return fmt.Sprintf(
+ "Control Type: %s (%q) Criticality: %t",
+ ControlTypeMap[ControlTypeManageDsaIT],
+ ControlTypeManageDsaIT,
+ c.Criticality)
+}
+
+func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT {
+ return &ControlManageDsaIT{Criticality: Criticality}
+}
+
+func FindControl(controls []Control, controlType string) Control {
+ for _, c := range controls {
+ if c.GetControlType() == controlType {
+ return c
+ }
+ }
+ return nil
+}
+
+func DecodeControl(packet *ber.Packet) Control {
+ ControlType := packet.Children[0].Value.(string)
+ Criticality := false
+
+ packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
+ value := packet.Children[1]
+ if len(packet.Children) == 3 {
+ value = packet.Children[2]
+ packet.Children[1].Description = "Criticality"
+ Criticality = packet.Children[1].Value.(bool)
+ }
+
+ value.Description = "Control Value"
+ switch ControlType {
+ case ControlTypePaging:
+ value.Description += " (Paging)"
+ c := new(ControlPaging)
+ if value.Value != nil {
+ valueChildren := ber.DecodePacket(value.Data.Bytes())
+ value.Data.Truncate(0)
+ value.Value = nil
+ value.AppendChild(valueChildren)
+ }
+ value = value.Children[0]
+ value.Description = "Search Control Value"
+ value.Children[0].Description = "Paging Size"
+ value.Children[1].Description = "Cookie"
+ c.PagingSize = uint32(value.Children[0].Value.(int64))
+ c.Cookie = value.Children[1].Data.Bytes()
+ value.Children[1].Value = c.Cookie
+ return c
+ case ControlTypeBeheraPasswordPolicy:
+ value.Description += " (Password Policy - Behera)"
+ c := NewControlBeheraPasswordPolicy()
+ if value.Value != nil {
+ valueChildren := ber.DecodePacket(value.Data.Bytes())
+ value.Data.Truncate(0)
+ value.Value = nil
+ value.AppendChild(valueChildren)
+ }
+
+ sequence := value.Children[0]
+
+ for _, child := range sequence.Children {
+ if child.Tag == 0 {
+ //Warning
+ child := child.Children[0]
+ packet := ber.DecodePacket(child.Data.Bytes())
+ val, ok := packet.Value.(int64)
+ if ok {
+ if child.Tag == 0 {
+ //timeBeforeExpiration
+ c.Expire = val
+ child.Value = c.Expire
+ } else if child.Tag == 1 {
+ //graceAuthNsRemaining
+ c.Grace = val
+ child.Value = c.Grace
+ }
+ }
+ } else if child.Tag == 1 {
+ // Error
+ packet := ber.DecodePacket(child.Data.Bytes())
+ val, ok := packet.Value.(int8)
+ if !ok {
+ // what to do?
+ val = -1
+ }
+ c.Error = val
+ child.Value = c.Error
+ c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error]
+ }
+ }
+ return c
+ case ControlTypeVChuPasswordMustChange:
+ c := &ControlVChuPasswordMustChange{MustChange: true}
+ return c
+ case ControlTypeVChuPasswordWarning:
+ c := &ControlVChuPasswordWarning{Expire: -1}
+ expireStr := ber.DecodeString(value.Data.Bytes())
+
+ expire, err := strconv.ParseInt(expireStr, 10, 64)
+ if err != nil {
+ return nil
+ }
+ c.Expire = expire
+ value.Value = c.Expire
+
+ return c
+ }
+ c := new(ControlString)
+ c.ControlType = ControlType
+ c.Criticality = Criticality
+ c.ControlValue = value.Value.(string)
+ return c
+}
+
+func NewControlString(controlType string, criticality bool, controlValue string) *ControlString {
+ return &ControlString{
+ ControlType: controlType,
+ Criticality: criticality,
+ ControlValue: controlValue,
+ }
+}
+
+func NewControlPaging(pagingSize uint32) *ControlPaging {
+ return &ControlPaging{PagingSize: pagingSize}
+}
+
+func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy {
+ return &ControlBeheraPasswordPolicy{
+ Expire: -1,
+ Grace: -1,
+ Error: -1,
+ }
+}
+
+func encodeControls(controls []Control) *ber.Packet {
+ packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls")
+ for _, control := range controls {
+ packet.AppendChild(control.Encode())
+ }
+ return packet
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/debug.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/debug.go
new file mode 100644
index 000000000..b8a7ecbff
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/debug.go
@@ -0,0 +1,24 @@
+package ldap
+
+import (
+ "log"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+// debbuging type
+// - has a Printf method to write the debug output
+type debugging bool
+
+// write debug output
+func (debug debugging) Printf(format string, args ...interface{}) {
+ if debug {
+ log.Printf(format, args...)
+ }
+}
+
+func (debug debugging) PrintPacket(packet *ber.Packet) {
+ if debug {
+ ber.PrintPacket(packet)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/del.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/del.go
new file mode 100644
index 000000000..2f0eae1cd
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/del.go
@@ -0,0 +1,79 @@
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// DelRequest ::= [APPLICATION 10] LDAPDN
+
+package ldap
+
+import (
+ "errors"
+ "log"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+type DelRequest struct {
+ DN string
+ Controls []Control
+}
+
+func (d DelRequest) encode() *ber.Packet {
+ request := ber.Encode(ber.ClassApplication, ber.TypePrimitive, ApplicationDelRequest, d.DN, "Del Request")
+ request.Data.Write([]byte(d.DN))
+ return request
+}
+
+func NewDelRequest(DN string,
+ Controls []Control) *DelRequest {
+ return &DelRequest{
+ DN: DN,
+ Controls: Controls,
+ }
+}
+
+func (l *Conn) Del(delRequest *DelRequest) error {
+ messageID := l.nextMessageID()
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+ packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+ packet.AppendChild(delRequest.encode())
+ if delRequest.Controls != nil {
+ packet.AppendChild(encodeControls(delRequest.Controls))
+ }
+
+ l.Debug.PrintPacket(packet)
+
+ channel, err := l.sendMessage(packet)
+ if err != nil {
+ return err
+ }
+ if channel == nil {
+ return NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+ }
+ defer l.finishMessage(messageID)
+
+ l.Debug.Printf("%d: waiting for response", messageID)
+ packet = <-channel
+ l.Debug.Printf("%d: got response %p", messageID, packet)
+ if packet == nil {
+ return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
+ }
+
+ if l.Debug {
+ if err := addLDAPDescriptions(packet); err != nil {
+ return err
+ }
+ ber.PrintPacket(packet)
+ }
+
+ if packet.Children[1].Tag == ApplicationDelResponse {
+ resultCode, resultDescription := getLDAPResultCode(packet)
+ if resultCode != 0 {
+ return NewError(resultCode, errors.New(resultDescription))
+ }
+ } else {
+ log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+ }
+
+ l.Debug.Printf("%d: returning", messageID)
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go
new file mode 100644
index 000000000..5d83c5e9a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go
@@ -0,0 +1,155 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+// File contains DN parsing functionallity
+//
+// https://tools.ietf.org/html/rfc4514
+//
+// distinguishedName = [ relativeDistinguishedName
+// *( COMMA relativeDistinguishedName ) ]
+// relativeDistinguishedName = attributeTypeAndValue
+// *( PLUS attributeTypeAndValue )
+// attributeTypeAndValue = attributeType EQUALS attributeValue
+// attributeType = descr / numericoid
+// attributeValue = string / hexstring
+//
+// ; The following characters are to be escaped when they appear
+// ; in the value to be encoded: ESC, one of <escaped>, leading
+// ; SHARP or SPACE, trailing SPACE, and NULL.
+// string = [ ( leadchar / pair ) [ *( stringchar / pair )
+// ( trailchar / pair ) ] ]
+//
+// leadchar = LUTF1 / UTFMB
+// LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A /
+// %x3D / %x3F-5B / %x5D-7F
+//
+// trailchar = TUTF1 / UTFMB
+// TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A /
+// %x3D / %x3F-5B / %x5D-7F
+//
+// stringchar = SUTF1 / UTFMB
+// SUTF1 = %x01-21 / %x23-2A / %x2D-3A /
+// %x3D / %x3F-5B / %x5D-7F
+//
+// pair = ESC ( ESC / special / hexpair )
+// special = escaped / SPACE / SHARP / EQUALS
+// escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE
+// hexstring = SHARP 1*hexpair
+// hexpair = HEX HEX
+//
+// where the productions <descr>, <numericoid>, <COMMA>, <DQUOTE>,
+// <EQUALS>, <ESC>, <HEX>, <LANGLE>, <NULL>, <PLUS>, <RANGLE>, <SEMI>,
+// <SPACE>, <SHARP>, and <UTFMB> are defined in [RFC4512].
+//
+
+package ldap
+
+import (
+ "bytes"
+ enchex "encoding/hex"
+ "errors"
+ "fmt"
+ "strings"
+
+ ber "gopkg.in/asn1-ber.v1"
+)
+
+type AttributeTypeAndValue struct {
+ Type string
+ Value string
+}
+
+type RelativeDN struct {
+ Attributes []*AttributeTypeAndValue
+}
+
+type DN struct {
+ RDNs []*RelativeDN
+}
+
+func ParseDN(str string) (*DN, error) {
+ dn := new(DN)
+ dn.RDNs = make([]*RelativeDN, 0)
+ rdn := new(RelativeDN)
+ rdn.Attributes = make([]*AttributeTypeAndValue, 0)
+ buffer := bytes.Buffer{}
+ attribute := new(AttributeTypeAndValue)
+ escaping := false
+
+ for i := 0; i < len(str); i++ {
+ char := str[i]
+ if escaping {
+ escaping = false
+ switch char {
+ case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\':
+ buffer.WriteByte(char)
+ continue
+ }
+ // Not a special character, assume hex encoded octet
+ if len(str) == i+1 {
+ return nil, errors.New("Got corrupted escaped character")
+ }
+
+ dst := []byte{0}
+ n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2]))
+ if err != nil {
+ return nil, errors.New(
+ fmt.Sprintf("Failed to decode escaped character: %s", err))
+ } else if n != 1 {
+ return nil, errors.New(
+ fmt.Sprintf("Expected 1 byte when un-escaping, got %d", n))
+ }
+ buffer.WriteByte(dst[0])
+ i++
+ } else if char == '\\' {
+ escaping = true
+ } else if char == '=' {
+ attribute.Type = buffer.String()
+ buffer.Reset()
+ // Special case: If the first character in the value is # the
+ // following data is BER encoded so we can just fast forward
+ // and decode.
+ if len(str) > i+1 && str[i+1] == '#' {
+ i += 2
+ index := strings.IndexAny(str[i:], ",+")
+ data := str
+ if index > 0 {
+ data = str[i : i+index]
+ } else {
+ data = str[i:]
+ }
+ raw_ber, err := enchex.DecodeString(data)
+ if err != nil {
+ return nil, errors.New(
+ fmt.Sprintf("Failed to decode BER encoding: %s", err))
+ }
+ packet := ber.DecodePacket(raw_ber)
+ buffer.WriteString(packet.Data.String())
+ i += len(data) - 1
+ }
+ } else if char == ',' || char == '+' {
+ // We're done with this RDN or value, push it
+ attribute.Value = buffer.String()
+ rdn.Attributes = append(rdn.Attributes, attribute)
+ attribute = new(AttributeTypeAndValue)
+ if char == ',' {
+ dn.RDNs = append(dn.RDNs, rdn)
+ rdn = new(RelativeDN)
+ rdn.Attributes = make([]*AttributeTypeAndValue, 0)
+ }
+ buffer.Reset()
+ } else {
+ buffer.WriteByte(char)
+ }
+ }
+ if buffer.Len() > 0 {
+ if len(attribute.Type) == 0 {
+ return nil, errors.New("DN ended with incomplete type, value pair")
+ }
+ attribute.Value = buffer.String()
+ rdn.Attributes = append(rdn.Attributes, attribute)
+ dn.RDNs = append(dn.RDNs, rdn)
+ }
+ return dn, nil
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go
new file mode 100644
index 000000000..39817c427
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go
@@ -0,0 +1,70 @@
+package ldap_test
+
+import (
+ "reflect"
+ "testing"
+
+ "gopkg.in/ldap.v2"
+)
+
+func TestSuccessfulDNParsing(t *testing.T) {
+ testcases := map[string]ldap.DN{
+ "": ldap.DN{[]*ldap.RelativeDN{}},
+ "cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": ldap.DN{[]*ldap.RelativeDN{
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"cn", "Jim, \"Hasse Hö\" Hansson!"}}},
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "dummy"}}},
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"dc", "com"}}}}},
+ "UID=jsmith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"UID", "jsmith"}}},
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}},
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
+ "OU=Sales+CN=J. Smith,DC=example,DC=net": ldap.DN{[]*ldap.RelativeDN{
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{
+ &ldap.AttributeTypeAndValue{"OU", "Sales"},
+ &ldap.AttributeTypeAndValue{"CN", "J. Smith"}}},
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "example"}}},
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
+ "1.3.6.1.4.1.1466.0=#04024869": ldap.DN{[]*ldap.RelativeDN{
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}}}},
+ "1.3.6.1.4.1.1466.0=#04024869,DC=net": ldap.DN{[]*ldap.RelativeDN{
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"}}},
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"DC", "net"}}}}},
+ "CN=Lu\\C4\\8Di\\C4\\87": ldap.DN{[]*ldap.RelativeDN{
+ &ldap.RelativeDN{[]*ldap.AttributeTypeAndValue{&ldap.AttributeTypeAndValue{"CN", "LuÄić"}}}}},
+ }
+
+ for test, answer := range testcases {
+ dn, err := ldap.ParseDN(test)
+ if err != nil {
+ t.Errorf(err.Error())
+ continue
+ }
+ if !reflect.DeepEqual(dn, &answer) {
+ t.Errorf("Parsed DN %s is not equal to the expected structure", test)
+ for _, rdn := range dn.RDNs {
+ for _, attribs := range rdn.Attributes {
+ t.Logf("#%v\n", attribs)
+ }
+ }
+ }
+ }
+}
+
+func TestErrorDNParsing(t *testing.T) {
+ testcases := map[string]string{
+ "*": "DN ended with incomplete type, value pair",
+ "cn=Jim\\0Test": "Failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'",
+ "cn=Jim\\0": "Got corrupted escaped character",
+ "DC=example,=net": "DN ended with incomplete type, value pair",
+ "1=#0402486": "Failed to decode BER encoding: encoding/hex: odd length hex string",
+ }
+
+ for test, answer := range testcases {
+ _, err := ldap.ParseDN(test)
+ if err == nil {
+ t.Errorf("Expected %s to fail parsing but succeeded\n", test)
+ } else if err.Error() != answer {
+ t.Errorf("Unexpected error on %s:\n%s\nvs.\n%s\n", test, answer, err.Error())
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/doc.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/doc.go
new file mode 100644
index 000000000..f20d39bc9
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/doc.go
@@ -0,0 +1,4 @@
+/*
+Package ldap provides basic LDAP v3 functionality.
+*/
+package ldap
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/error.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/error.go
new file mode 100644
index 000000000..2dbc30ac0
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/error.go
@@ -0,0 +1,137 @@
+package ldap
+
+import (
+ "fmt"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+// LDAP Result Codes
+const (
+ LDAPResultSuccess = 0
+ LDAPResultOperationsError = 1
+ LDAPResultProtocolError = 2
+ LDAPResultTimeLimitExceeded = 3
+ LDAPResultSizeLimitExceeded = 4
+ LDAPResultCompareFalse = 5
+ LDAPResultCompareTrue = 6
+ LDAPResultAuthMethodNotSupported = 7
+ LDAPResultStrongAuthRequired = 8
+ LDAPResultReferral = 10
+ LDAPResultAdminLimitExceeded = 11
+ LDAPResultUnavailableCriticalExtension = 12
+ LDAPResultConfidentialityRequired = 13
+ LDAPResultSaslBindInProgress = 14
+ LDAPResultNoSuchAttribute = 16
+ LDAPResultUndefinedAttributeType = 17
+ LDAPResultInappropriateMatching = 18
+ LDAPResultConstraintViolation = 19
+ LDAPResultAttributeOrValueExists = 20
+ LDAPResultInvalidAttributeSyntax = 21
+ LDAPResultNoSuchObject = 32
+ LDAPResultAliasProblem = 33
+ LDAPResultInvalidDNSyntax = 34
+ LDAPResultAliasDereferencingProblem = 36
+ LDAPResultInappropriateAuthentication = 48
+ LDAPResultInvalidCredentials = 49
+ LDAPResultInsufficientAccessRights = 50
+ LDAPResultBusy = 51
+ LDAPResultUnavailable = 52
+ LDAPResultUnwillingToPerform = 53
+ LDAPResultLoopDetect = 54
+ LDAPResultNamingViolation = 64
+ LDAPResultObjectClassViolation = 65
+ LDAPResultNotAllowedOnNonLeaf = 66
+ LDAPResultNotAllowedOnRDN = 67
+ LDAPResultEntryAlreadyExists = 68
+ LDAPResultObjectClassModsProhibited = 69
+ LDAPResultAffectsMultipleDSAs = 71
+ LDAPResultOther = 80
+
+ ErrorNetwork = 200
+ ErrorFilterCompile = 201
+ ErrorFilterDecompile = 202
+ ErrorDebugging = 203
+ ErrorUnexpectedMessage = 204
+ ErrorUnexpectedResponse = 205
+)
+
+var LDAPResultCodeMap = map[uint8]string{
+ LDAPResultSuccess: "Success",
+ LDAPResultOperationsError: "Operations Error",
+ LDAPResultProtocolError: "Protocol Error",
+ LDAPResultTimeLimitExceeded: "Time Limit Exceeded",
+ LDAPResultSizeLimitExceeded: "Size Limit Exceeded",
+ LDAPResultCompareFalse: "Compare False",
+ LDAPResultCompareTrue: "Compare True",
+ LDAPResultAuthMethodNotSupported: "Auth Method Not Supported",
+ LDAPResultStrongAuthRequired: "Strong Auth Required",
+ LDAPResultReferral: "Referral",
+ LDAPResultAdminLimitExceeded: "Admin Limit Exceeded",
+ LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension",
+ LDAPResultConfidentialityRequired: "Confidentiality Required",
+ LDAPResultSaslBindInProgress: "Sasl Bind In Progress",
+ LDAPResultNoSuchAttribute: "No Such Attribute",
+ LDAPResultUndefinedAttributeType: "Undefined Attribute Type",
+ LDAPResultInappropriateMatching: "Inappropriate Matching",
+ LDAPResultConstraintViolation: "Constraint Violation",
+ LDAPResultAttributeOrValueExists: "Attribute Or Value Exists",
+ LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax",
+ LDAPResultNoSuchObject: "No Such Object",
+ LDAPResultAliasProblem: "Alias Problem",
+ LDAPResultInvalidDNSyntax: "Invalid DN Syntax",
+ LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem",
+ LDAPResultInappropriateAuthentication: "Inappropriate Authentication",
+ LDAPResultInvalidCredentials: "Invalid Credentials",
+ LDAPResultInsufficientAccessRights: "Insufficient Access Rights",
+ LDAPResultBusy: "Busy",
+ LDAPResultUnavailable: "Unavailable",
+ LDAPResultUnwillingToPerform: "Unwilling To Perform",
+ LDAPResultLoopDetect: "Loop Detect",
+ LDAPResultNamingViolation: "Naming Violation",
+ LDAPResultObjectClassViolation: "Object Class Violation",
+ LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf",
+ LDAPResultNotAllowedOnRDN: "Not Allowed On RDN",
+ LDAPResultEntryAlreadyExists: "Entry Already Exists",
+ LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited",
+ LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs",
+ LDAPResultOther: "Other",
+}
+
+func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) {
+ if len(packet.Children) >= 2 {
+ response := packet.Children[1]
+ if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 {
+ // Children[1].Children[2] is the diagnosticMessage which is guaranteed to exist as seen here: https://tools.ietf.org/html/rfc4511#section-4.1.9
+ return uint8(response.Children[0].Value.(int64)), response.Children[2].Value.(string)
+ }
+ }
+
+ return ErrorNetwork, "Invalid packet format"
+}
+
+type Error struct {
+ Err error
+ ResultCode uint8
+}
+
+func (e *Error) Error() string {
+ return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error())
+}
+
+func NewError(resultCode uint8, err error) error {
+ return &Error{ResultCode: resultCode, Err: err}
+}
+
+func IsErrorWithCode(err error, desiredResultCode uint8) bool {
+ if err == nil {
+ return false
+ }
+
+ serverError, ok := err.(*Error)
+ if !ok {
+ return false
+ }
+
+ return serverError.ResultCode == desiredResultCode
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go
new file mode 100644
index 000000000..b018a9664
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go
@@ -0,0 +1,305 @@
+package ldap_test
+
+import (
+ "crypto/tls"
+ "fmt"
+ "log"
+
+ "gopkg.in/ldap.v2"
+)
+
+// ExampleConn_Bind demonstrates how to bind a connection to an ldap user
+// allowing access to restricted attrabutes that user has access to
+func ExampleConn_Bind() {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ err = l.Bind("cn=read-only-admin,dc=example,dc=com", "password")
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+// ExampleConn_Search demonstrates how to use the search interface
+func ExampleConn_Search() {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ searchRequest := ldap.NewSearchRequest(
+ "dc=example,dc=com", // The base dn to search
+ ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+ "(&(objectClass=organizationalPerson))", // The filter to apply
+ []string{"dn", "cn"}, // A list attributes to retrieve
+ nil,
+ )
+
+ sr, err := l.Search(searchRequest)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, entry := range sr.Entries {
+ fmt.Printf("%s: %v\n", entry.DN, entry.GetAttributeValue("cn"))
+ }
+}
+
+// ExampleStartTLS demonstrates how to start a TLS connection
+func ExampleConn_StartTLS() {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ // Reconnect with TLS
+ err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Opertations via l are now encrypted
+}
+
+// ExampleConn_Compare demonstrates how to comapre an attribute with a value
+func ExampleConn_Compare() {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ matched, err := l.Compare("cn=user,dc=example,dc=com", "uid", "someuserid")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println(matched)
+}
+
+func ExampleConn_PasswordModify_admin() {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ err = l.Bind("cn=admin,dc=example,dc=com", "password")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ passwordModifyRequest := ldap.NewPasswordModifyRequest("cn=user,dc=example,dc=com", "", "NewPassword")
+ _, err = l.PasswordModify(passwordModifyRequest)
+
+ if err != nil {
+ log.Fatalf("Password could not be changed: %s", err.Error())
+ }
+}
+
+func ExampleConn_PasswordModify_generatedPassword() {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ err = l.Bind("cn=user,dc=example,dc=com", "password")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ passwordModifyRequest := ldap.NewPasswordModifyRequest("", "OldPassword", "")
+ passwordModifyResponse, err := l.PasswordModify(passwordModifyRequest)
+ if err != nil {
+ log.Fatalf("Password could not be changed: %s", err.Error())
+ }
+
+ generatedPassword := passwordModifyResponse.GeneratedPassword
+ log.Printf("Generated password: %s\n", generatedPassword)
+}
+
+func ExampleConn_PasswordModify_setNewPassword() {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ err = l.Bind("cn=user,dc=example,dc=com", "password")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ passwordModifyRequest := ldap.NewPasswordModifyRequest("", "OldPassword", "NewPassword")
+ _, err = l.PasswordModify(passwordModifyRequest)
+
+ if err != nil {
+ log.Fatalf("Password could not be changed: %s", err.Error())
+ }
+}
+
+func ExampleConn_Modify() {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ // Add a description, and replace the mail attributes
+ modify := ldap.NewModifyRequest("cn=user,dc=example,dc=com")
+ modify.Add("description", []string{"An example user"})
+ modify.Replace("mail", []string{"user@example.org"})
+
+ err = l.Modify(modify)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+// Example User Authentication shows how a typical application can verify a login attempt
+func Example_userAuthentication() {
+ // The username and password we want to check
+ username := "someuser"
+ password := "userpassword"
+
+ bindusername := "readonly"
+ bindpassword := "password"
+
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ // Reconnect with TLS
+ err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // First bind with a read only user
+ err = l.Bind(bindusername, bindpassword)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Search for the given username
+ searchRequest := ldap.NewSearchRequest(
+ "dc=example,dc=com",
+ ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+ fmt.Sprintf("(&(objectClass=organizationalPerson)&(uid=%s))", username),
+ []string{"dn"},
+ nil,
+ )
+
+ sr, err := l.Search(searchRequest)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if len(sr.Entries) != 1 {
+ log.Fatal("User does not exist or too many entries returned")
+ }
+
+ userdn := sr.Entries[0].DN
+
+ // Bind as the user to verify their password
+ err = l.Bind(userdn, password)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Rebind as the read only user for any futher queries
+ err = l.Bind(bindusername, bindpassword)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func Example_beherappolicy() {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+
+ controls := []ldap.Control{}
+ controls = append(controls, ldap.NewControlBeheraPasswordPolicy())
+ bindRequest := ldap.NewSimpleBindRequest("cn=admin,dc=example,dc=com", "password", controls)
+
+ r, err := l.SimpleBind(bindRequest)
+ ppolicyControl := ldap.FindControl(r.Controls, ldap.ControlTypeBeheraPasswordPolicy)
+
+ var ppolicy *ldap.ControlBeheraPasswordPolicy
+ if ppolicyControl != nil {
+ ppolicy = ppolicyControl.(*ldap.ControlBeheraPasswordPolicy)
+ } else {
+ log.Printf("ppolicyControl response not avaliable.\n")
+ }
+ if err != nil {
+ errStr := "ERROR: Cannot bind: " + err.Error()
+ if ppolicy != nil && ppolicy.Error >= 0 {
+ errStr += ":" + ppolicy.ErrorString
+ }
+ log.Print(errStr)
+ } else {
+ logStr := "Login Ok"
+ if ppolicy != nil {
+ if ppolicy.Expire >= 0 {
+ logStr += fmt.Sprintf(". Password expires in %d seconds\n", ppolicy.Expire)
+ } else if ppolicy.Grace >= 0 {
+ logStr += fmt.Sprintf(". Password expired, %d grace logins remain\n", ppolicy.Grace)
+ }
+ }
+ log.Print(logStr)
+ }
+}
+
+func Example_vchuppolicy() {
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+ l.Debug = true
+
+ bindRequest := ldap.NewSimpleBindRequest("cn=admin,dc=example,dc=com", "password", nil)
+
+ r, err := l.SimpleBind(bindRequest)
+
+ passwordMustChangeControl := ldap.FindControl(r.Controls, ldap.ControlTypeVChuPasswordMustChange)
+ var passwordMustChange *ldap.ControlVChuPasswordMustChange
+ if passwordMustChangeControl != nil {
+ passwordMustChange = passwordMustChangeControl.(*ldap.ControlVChuPasswordMustChange)
+ }
+
+ if passwordMustChange != nil && passwordMustChange.MustChange {
+ log.Printf("Password Must be changed.\n")
+ }
+
+ passwordWarningControl := ldap.FindControl(r.Controls, ldap.ControlTypeVChuPasswordWarning)
+
+ var passwordWarning *ldap.ControlVChuPasswordWarning
+ if passwordWarningControl != nil {
+ passwordWarning = passwordWarningControl.(*ldap.ControlVChuPasswordWarning)
+ } else {
+ log.Printf("ppolicyControl response not available.\n")
+ }
+ if err != nil {
+ log.Print("ERROR: Cannot bind: " + err.Error())
+ } else {
+ logStr := "Login Ok"
+ if passwordWarning != nil {
+ if passwordWarning.Expire >= 0 {
+ logStr += fmt.Sprintf(". Password expires in %d seconds\n", passwordWarning.Expire)
+ }
+ }
+ log.Print(logStr)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go
new file mode 100644
index 000000000..63bcec1e3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go
@@ -0,0 +1,456 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ldap
+
+import (
+ "bytes"
+ hexpac "encoding/hex"
+ "errors"
+ "fmt"
+ "strings"
+ "unicode/utf8"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+const (
+ FilterAnd = 0
+ FilterOr = 1
+ FilterNot = 2
+ FilterEqualityMatch = 3
+ FilterSubstrings = 4
+ FilterGreaterOrEqual = 5
+ FilterLessOrEqual = 6
+ FilterPresent = 7
+ FilterApproxMatch = 8
+ FilterExtensibleMatch = 9
+)
+
+var FilterMap = map[uint64]string{
+ FilterAnd: "And",
+ FilterOr: "Or",
+ FilterNot: "Not",
+ FilterEqualityMatch: "Equality Match",
+ FilterSubstrings: "Substrings",
+ FilterGreaterOrEqual: "Greater Or Equal",
+ FilterLessOrEqual: "Less Or Equal",
+ FilterPresent: "Present",
+ FilterApproxMatch: "Approx Match",
+ FilterExtensibleMatch: "Extensible Match",
+}
+
+const (
+ FilterSubstringsInitial = 0
+ FilterSubstringsAny = 1
+ FilterSubstringsFinal = 2
+)
+
+var FilterSubstringsMap = map[uint64]string{
+ FilterSubstringsInitial: "Substrings Initial",
+ FilterSubstringsAny: "Substrings Any",
+ FilterSubstringsFinal: "Substrings Final",
+}
+
+const (
+ MatchingRuleAssertionMatchingRule = 1
+ MatchingRuleAssertionType = 2
+ MatchingRuleAssertionMatchValue = 3
+ MatchingRuleAssertionDNAttributes = 4
+)
+
+var MatchingRuleAssertionMap = map[uint64]string{
+ MatchingRuleAssertionMatchingRule: "Matching Rule Assertion Matching Rule",
+ MatchingRuleAssertionType: "Matching Rule Assertion Type",
+ MatchingRuleAssertionMatchValue: "Matching Rule Assertion Match Value",
+ MatchingRuleAssertionDNAttributes: "Matching Rule Assertion DN Attributes",
+}
+
+func CompileFilter(filter string) (*ber.Packet, error) {
+ if len(filter) == 0 || filter[0] != '(' {
+ return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('"))
+ }
+ packet, pos, err := compileFilter(filter, 1)
+ if err != nil {
+ return nil, err
+ }
+ if pos != len(filter) {
+ return nil, NewError(ErrorFilterCompile, errors.New("ldap: finished compiling filter with extra at end: "+fmt.Sprint(filter[pos:])))
+ }
+ return packet, nil
+}
+
+func DecompileFilter(packet *ber.Packet) (ret string, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter"))
+ }
+ }()
+ ret = "("
+ err = nil
+ childStr := ""
+
+ switch packet.Tag {
+ case FilterAnd:
+ ret += "&"
+ for _, child := range packet.Children {
+ childStr, err = DecompileFilter(child)
+ if err != nil {
+ return
+ }
+ ret += childStr
+ }
+ case FilterOr:
+ ret += "|"
+ for _, child := range packet.Children {
+ childStr, err = DecompileFilter(child)
+ if err != nil {
+ return
+ }
+ ret += childStr
+ }
+ case FilterNot:
+ ret += "!"
+ childStr, err = DecompileFilter(packet.Children[0])
+ if err != nil {
+ return
+ }
+ ret += childStr
+
+ case FilterSubstrings:
+ ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+ ret += "="
+ for i, child := range packet.Children[1].Children {
+ if i == 0 && child.Tag != FilterSubstringsInitial {
+ ret += "*"
+ }
+ ret += EscapeFilter(ber.DecodeString(child.Data.Bytes()))
+ if child.Tag != FilterSubstringsFinal {
+ ret += "*"
+ }
+ }
+ case FilterEqualityMatch:
+ ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+ ret += "="
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+ case FilterGreaterOrEqual:
+ ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+ ret += ">="
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+ case FilterLessOrEqual:
+ ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+ ret += "<="
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+ case FilterPresent:
+ ret += ber.DecodeString(packet.Data.Bytes())
+ ret += "=*"
+ case FilterApproxMatch:
+ ret += ber.DecodeString(packet.Children[0].Data.Bytes())
+ ret += "~="
+ ret += EscapeFilter(ber.DecodeString(packet.Children[1].Data.Bytes()))
+ case FilterExtensibleMatch:
+ attr := ""
+ dnAttributes := false
+ matchingRule := ""
+ value := ""
+
+ for _, child := range packet.Children {
+ switch child.Tag {
+ case MatchingRuleAssertionMatchingRule:
+ matchingRule = ber.DecodeString(child.Data.Bytes())
+ case MatchingRuleAssertionType:
+ attr = ber.DecodeString(child.Data.Bytes())
+ case MatchingRuleAssertionMatchValue:
+ value = ber.DecodeString(child.Data.Bytes())
+ case MatchingRuleAssertionDNAttributes:
+ dnAttributes = child.Value.(bool)
+ }
+ }
+
+ if len(attr) > 0 {
+ ret += attr
+ }
+ if dnAttributes {
+ ret += ":dn"
+ }
+ if len(matchingRule) > 0 {
+ ret += ":"
+ ret += matchingRule
+ }
+ ret += ":="
+ ret += EscapeFilter(value)
+ }
+
+ ret += ")"
+ return
+}
+
+func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) {
+ for pos < len(filter) && filter[pos] == '(' {
+ child, newPos, err := compileFilter(filter, pos+1)
+ if err != nil {
+ return pos, err
+ }
+ pos = newPos
+ parent.AppendChild(child)
+ }
+ if pos == len(filter) {
+ return pos, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
+ }
+
+ return pos + 1, nil
+}
+
+func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
+ var (
+ packet *ber.Packet
+ err error
+ )
+
+ defer func() {
+ if r := recover(); r != nil {
+ err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter"))
+ }
+ }()
+ newPos := pos
+
+ currentRune, currentWidth := utf8.DecodeRuneInString(filter[newPos:])
+
+ switch currentRune {
+ case utf8.RuneError:
+ return nil, 0, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos))
+ case '(':
+ packet, newPos, err = compileFilter(filter, pos+currentWidth)
+ newPos++
+ return packet, newPos, err
+ case '&':
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd])
+ newPos, err = compileFilterSet(filter, pos+currentWidth, packet)
+ return packet, newPos, err
+ case '|':
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr])
+ newPos, err = compileFilterSet(filter, pos+currentWidth, packet)
+ return packet, newPos, err
+ case '!':
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot])
+ var child *ber.Packet
+ child, newPos, err = compileFilter(filter, pos+currentWidth)
+ packet.AppendChild(child)
+ return packet, newPos, err
+ default:
+ READING_ATTR := 0
+ READING_EXTENSIBLE_MATCHING_RULE := 1
+ READING_CONDITION := 2
+
+ state := READING_ATTR
+
+ attribute := ""
+ extensibleDNAttributes := false
+ extensibleMatchingRule := ""
+ condition := ""
+
+ for newPos < len(filter) {
+ remainingFilter := filter[newPos:]
+ currentRune, currentWidth = utf8.DecodeRuneInString(remainingFilter)
+ if currentRune == ')' {
+ break
+ }
+ if currentRune == utf8.RuneError {
+ return packet, newPos, NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", newPos))
+ }
+
+ switch state {
+ case READING_ATTR:
+ switch {
+ // Extensible rule, with only DN-matching
+ case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:="):
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
+ extensibleDNAttributes = true
+ state = READING_CONDITION
+ newPos += 5
+
+ // Extensible rule, with DN-matching and a matching OID
+ case currentRune == ':' && strings.HasPrefix(remainingFilter, ":dn:"):
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
+ extensibleDNAttributes = true
+ state = READING_EXTENSIBLE_MATCHING_RULE
+ newPos += 4
+
+ // Extensible rule, with attr only
+ case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="):
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
+ state = READING_CONDITION
+ newPos += 2
+
+ // Extensible rule, with no DN attribute matching
+ case currentRune == ':':
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterExtensibleMatch, nil, FilterMap[FilterExtensibleMatch])
+ state = READING_EXTENSIBLE_MATCHING_RULE
+ newPos += 1
+
+ // Equality condition
+ case currentRune == '=':
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch])
+ state = READING_CONDITION
+ newPos += 1
+
+ // Greater-than or equal
+ case currentRune == '>' && strings.HasPrefix(remainingFilter, ">="):
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual])
+ state = READING_CONDITION
+ newPos += 2
+
+ // Less-than or equal
+ case currentRune == '<' && strings.HasPrefix(remainingFilter, "<="):
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual])
+ state = READING_CONDITION
+ newPos += 2
+
+ // Approx
+ case currentRune == '~' && strings.HasPrefix(remainingFilter, "~="):
+ packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterApproxMatch])
+ state = READING_CONDITION
+ newPos += 2
+
+ // Still reading the attribute name
+ default:
+ attribute += fmt.Sprintf("%c", currentRune)
+ newPos += currentWidth
+ }
+
+ case READING_EXTENSIBLE_MATCHING_RULE:
+ switch {
+
+ // Matching rule OID is done
+ case currentRune == ':' && strings.HasPrefix(remainingFilter, ":="):
+ state = READING_CONDITION
+ newPos += 2
+
+ // Still reading the matching rule oid
+ default:
+ extensibleMatchingRule += fmt.Sprintf("%c", currentRune)
+ newPos += currentWidth
+ }
+
+ case READING_CONDITION:
+ // append to the condition
+ condition += fmt.Sprintf("%c", currentRune)
+ newPos += currentWidth
+ }
+ }
+
+ if newPos == len(filter) {
+ err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
+ return packet, newPos, err
+ }
+ if packet == nil {
+ err = NewError(ErrorFilterCompile, errors.New("ldap: error parsing filter"))
+ return packet, newPos, err
+ }
+
+ switch {
+ case packet.Tag == FilterExtensibleMatch:
+ // MatchingRuleAssertion ::= SEQUENCE {
+ // matchingRule [1] MatchingRuleID OPTIONAL,
+ // type [2] AttributeDescription OPTIONAL,
+ // matchValue [3] AssertionValue,
+ // dnAttributes [4] BOOLEAN DEFAULT FALSE
+ // }
+
+ // Include the matching rule oid, if specified
+ if len(extensibleMatchingRule) > 0 {
+ packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchingRule, extensibleMatchingRule, MatchingRuleAssertionMap[MatchingRuleAssertionMatchingRule]))
+ }
+
+ // Include the attribute, if specified
+ if len(attribute) > 0 {
+ packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionType, attribute, MatchingRuleAssertionMap[MatchingRuleAssertionType]))
+ }
+
+ // Add the value (only required child)
+ encodedString, err := escapedStringToEncodedBytes(condition)
+ if err != nil {
+ return packet, newPos, err
+ }
+ packet.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionMatchValue, encodedString, MatchingRuleAssertionMap[MatchingRuleAssertionMatchValue]))
+
+ // Defaults to false, so only include in the sequence if true
+ if extensibleDNAttributes {
+ packet.AppendChild(ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, MatchingRuleAssertionDNAttributes, extensibleDNAttributes, MatchingRuleAssertionMap[MatchingRuleAssertionDNAttributes]))
+ }
+
+ case packet.Tag == FilterEqualityMatch && condition == "*":
+ packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute, FilterMap[FilterPresent])
+ case packet.Tag == FilterEqualityMatch && strings.Contains(condition, "*"):
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
+ packet.Tag = FilterSubstrings
+ packet.Description = FilterMap[uint64(packet.Tag)]
+ seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
+ parts := strings.Split(condition, "*")
+ for i, part := range parts {
+ if part == "" {
+ continue
+ }
+ var tag ber.Tag
+ switch i {
+ case 0:
+ tag = FilterSubstringsInitial
+ case len(parts) - 1:
+ tag = FilterSubstringsFinal
+ default:
+ tag = FilterSubstringsAny
+ }
+ encodedString, err := escapedStringToEncodedBytes(part)
+ if err != nil {
+ return packet, newPos, err
+ }
+ seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, encodedString, FilterSubstringsMap[uint64(tag)]))
+ }
+ packet.AppendChild(seq)
+ default:
+ encodedString, err := escapedStringToEncodedBytes(condition)
+ if err != nil {
+ return packet, newPos, err
+ }
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
+ packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, encodedString, "Condition"))
+ }
+
+ newPos += currentWidth
+ return packet, newPos, err
+ }
+}
+
+// Convert from "ABC\xx\xx\xx" form to literal bytes for transport
+func escapedStringToEncodedBytes(escapedString string) (string, error) {
+ var buffer bytes.Buffer
+ i := 0
+ for i < len(escapedString) {
+ currentRune, currentWidth := utf8.DecodeRuneInString(escapedString[i:])
+ if currentRune == utf8.RuneError {
+ return "", NewError(ErrorFilterCompile, fmt.Errorf("ldap: error reading rune at position %d", i))
+ }
+
+ // Check for escaped hex characters and convert them to their literal value for transport.
+ if currentRune == '\\' {
+ // http://tools.ietf.org/search/rfc4515
+ // \ (%x5C) is not a valid character unless it is followed by two HEX characters due to not
+ // being a member of UTF1SUBSET.
+ if i+2 > len(escapedString) {
+ return "", NewError(ErrorFilterCompile, errors.New("ldap: missing characters for escape in filter"))
+ }
+ if escByte, decodeErr := hexpac.DecodeString(escapedString[i+1 : i+3]); decodeErr != nil {
+ return "", NewError(ErrorFilterCompile, errors.New("ldap: invalid characters for escape in filter"))
+ } else {
+ buffer.WriteByte(escByte[0])
+ i += 2 // +1 from end of loop, so 3 total for \xx.
+ }
+ } else {
+ buffer.WriteRune(currentRune)
+ }
+
+ i += currentWidth
+ }
+ return buffer.String(), nil
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go
new file mode 100644
index 000000000..ae1b79b0c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go
@@ -0,0 +1,248 @@
+package ldap_test
+
+import (
+ "strings"
+ "testing"
+
+ "gopkg.in/asn1-ber.v1"
+ "gopkg.in/ldap.v2"
+)
+
+type compileTest struct {
+ filterStr string
+
+ expectedFilter string
+ expectedType int
+ expectedErr string
+}
+
+var testFilters = []compileTest{
+ compileTest{
+ filterStr: "(&(sn=Miller)(givenName=Bob))",
+ expectedFilter: "(&(sn=Miller)(givenName=Bob))",
+ expectedType: ldap.FilterAnd,
+ },
+ compileTest{
+ filterStr: "(|(sn=Miller)(givenName=Bob))",
+ expectedFilter: "(|(sn=Miller)(givenName=Bob))",
+ expectedType: ldap.FilterOr,
+ },
+ compileTest{
+ filterStr: "(!(sn=Miller))",
+ expectedFilter: "(!(sn=Miller))",
+ expectedType: ldap.FilterNot,
+ },
+ compileTest{
+ filterStr: "(sn=Miller)",
+ expectedFilter: "(sn=Miller)",
+ expectedType: ldap.FilterEqualityMatch,
+ },
+ compileTest{
+ filterStr: "(sn=Mill*)",
+ expectedFilter: "(sn=Mill*)",
+ expectedType: ldap.FilterSubstrings,
+ },
+ compileTest{
+ filterStr: "(sn=*Mill)",
+ expectedFilter: "(sn=*Mill)",
+ expectedType: ldap.FilterSubstrings,
+ },
+ compileTest{
+ filterStr: "(sn=*Mill*)",
+ expectedFilter: "(sn=*Mill*)",
+ expectedType: ldap.FilterSubstrings,
+ },
+ compileTest{
+ filterStr: "(sn=*i*le*)",
+ expectedFilter: "(sn=*i*le*)",
+ expectedType: ldap.FilterSubstrings,
+ },
+ compileTest{
+ filterStr: "(sn=Mi*l*r)",
+ expectedFilter: "(sn=Mi*l*r)",
+ expectedType: ldap.FilterSubstrings,
+ },
+ // substring filters escape properly
+ compileTest{
+ filterStr: `(sn=Mi*함*r)`,
+ expectedFilter: `(sn=Mi*\ed\95\a8*r)`,
+ expectedType: ldap.FilterSubstrings,
+ },
+ // already escaped substring filters don't get double-escaped
+ compileTest{
+ filterStr: `(sn=Mi*\ed\95\a8*r)`,
+ expectedFilter: `(sn=Mi*\ed\95\a8*r)`,
+ expectedType: ldap.FilterSubstrings,
+ },
+ compileTest{
+ filterStr: "(sn=Mi*le*)",
+ expectedFilter: "(sn=Mi*le*)",
+ expectedType: ldap.FilterSubstrings,
+ },
+ compileTest{
+ filterStr: "(sn=*i*ler)",
+ expectedFilter: "(sn=*i*ler)",
+ expectedType: ldap.FilterSubstrings,
+ },
+ compileTest{
+ filterStr: "(sn>=Miller)",
+ expectedFilter: "(sn>=Miller)",
+ expectedType: ldap.FilterGreaterOrEqual,
+ },
+ compileTest{
+ filterStr: "(sn<=Miller)",
+ expectedFilter: "(sn<=Miller)",
+ expectedType: ldap.FilterLessOrEqual,
+ },
+ compileTest{
+ filterStr: "(sn=*)",
+ expectedFilter: "(sn=*)",
+ expectedType: ldap.FilterPresent,
+ },
+ compileTest{
+ filterStr: "(sn~=Miller)",
+ expectedFilter: "(sn~=Miller)",
+ expectedType: ldap.FilterApproxMatch,
+ },
+ compileTest{
+ filterStr: `(objectGUID='\fc\fe\a3\ab\f9\90N\aaGm\d5I~\d12)`,
+ expectedFilter: `(objectGUID='\fc\fe\a3\ab\f9\90N\aaGm\d5I~\d12)`,
+ expectedType: ldap.FilterEqualityMatch,
+ },
+ compileTest{
+ filterStr: `(objectGUID=абвгдеёжзийклмнопрÑтуфхцчшщъыьÑÑŽÑ)`,
+ expectedFilter: `(objectGUID=\d0\b0\d0\b1\d0\b2\d0\b3\d0\b4\d0\b5\d1\91\d0\b6\d0\b7\d0\b8\d0\b9\d0\ba\d0\bb\d0\bc\d0\bd\d0\be\d0\bf\d1\80\d1\81\d1\82\d1\83\d1\84\d1\85\d1\86\d1\87\d1\88\d1\89\d1\8a\d1\8b\d1\8c\d1\8d\d1\8e\d1\8f)`,
+ expectedType: ldap.FilterEqualityMatch,
+ },
+ compileTest{
+ filterStr: `(objectGUID=함수목ë¡)`,
+ expectedFilter: `(objectGUID=\ed\95\a8\ec\88\98\eb\aa\a9\eb\a1\9d)`,
+ expectedType: ldap.FilterEqualityMatch,
+ },
+ compileTest{
+ filterStr: `(objectGUID=`,
+ expectedFilter: ``,
+ expectedType: 0,
+ expectedErr: "unexpected end of filter",
+ },
+ compileTest{
+ filterStr: `(objectGUID=함수목ë¡`,
+ expectedFilter: ``,
+ expectedType: 0,
+ expectedErr: "unexpected end of filter",
+ },
+ compileTest{
+ filterStr: `(&(objectclass=inetorgperson)(cn=中文))`,
+ expectedFilter: `(&(objectclass=inetorgperson)(cn=\e4\b8\ad\e6\96\87))`,
+ expectedType: 0,
+ },
+ // attr extension
+ compileTest{
+ filterStr: `(memberOf:=foo)`,
+ expectedFilter: `(memberOf:=foo)`,
+ expectedType: ldap.FilterExtensibleMatch,
+ },
+ // attr+named matching rule extension
+ compileTest{
+ filterStr: `(memberOf:test:=foo)`,
+ expectedFilter: `(memberOf:test:=foo)`,
+ expectedType: ldap.FilterExtensibleMatch,
+ },
+ // attr+oid matching rule extension
+ compileTest{
+ filterStr: `(cn:1.2.3.4.5:=Fred Flintstone)`,
+ expectedFilter: `(cn:1.2.3.4.5:=Fred Flintstone)`,
+ expectedType: ldap.FilterExtensibleMatch,
+ },
+ // attr+dn+oid matching rule extension
+ compileTest{
+ filterStr: `(sn:dn:2.4.6.8.10:=Barney Rubble)`,
+ expectedFilter: `(sn:dn:2.4.6.8.10:=Barney Rubble)`,
+ expectedType: ldap.FilterExtensibleMatch,
+ },
+ // attr+dn extension
+ compileTest{
+ filterStr: `(o:dn:=Ace Industry)`,
+ expectedFilter: `(o:dn:=Ace Industry)`,
+ expectedType: ldap.FilterExtensibleMatch,
+ },
+ // dn extension
+ compileTest{
+ filterStr: `(:dn:2.4.6.8.10:=Dino)`,
+ expectedFilter: `(:dn:2.4.6.8.10:=Dino)`,
+ expectedType: ldap.FilterExtensibleMatch,
+ },
+ compileTest{
+ filterStr: `(memberOf:1.2.840.113556.1.4.1941:=CN=User1,OU=blah,DC=mydomain,DC=net)`,
+ expectedFilter: `(memberOf:1.2.840.113556.1.4.1941:=CN=User1,OU=blah,DC=mydomain,DC=net)`,
+ expectedType: ldap.FilterExtensibleMatch,
+ },
+
+ // compileTest{ filterStr: "()", filterType: FilterExtensibleMatch },
+}
+
+var testInvalidFilters = []string{
+ `(objectGUID=\zz)`,
+ `(objectGUID=\a)`,
+}
+
+func TestFilter(t *testing.T) {
+ // Test Compiler and Decompiler
+ for _, i := range testFilters {
+ filter, err := ldap.CompileFilter(i.filterStr)
+ if err != nil {
+ if i.expectedErr == "" || !strings.Contains(err.Error(), i.expectedErr) {
+ t.Errorf("Problem compiling '%s' - '%v' (expected error to contain '%v')", i.filterStr, err, i.expectedErr)
+ }
+ } else if filter.Tag != ber.Tag(i.expectedType) {
+ t.Errorf("%q Expected %q got %q", i.filterStr, ldap.FilterMap[uint64(i.expectedType)], ldap.FilterMap[uint64(filter.Tag)])
+ } else {
+ o, err := ldap.DecompileFilter(filter)
+ if err != nil {
+ t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
+ } else if i.expectedFilter != o {
+ t.Errorf("%q expected, got %q", i.expectedFilter, o)
+ }
+ }
+ }
+}
+
+func TestInvalidFilter(t *testing.T) {
+ for _, filterStr := range testInvalidFilters {
+ if _, err := ldap.CompileFilter(filterStr); err == nil {
+ t.Errorf("Problem compiling %s - expected err", filterStr)
+ }
+ }
+}
+
+func BenchmarkFilterCompile(b *testing.B) {
+ b.StopTimer()
+ filters := make([]string, len(testFilters))
+
+ // Test Compiler and Decompiler
+ for idx, i := range testFilters {
+ filters[idx] = i.filterStr
+ }
+
+ maxIdx := len(filters)
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ ldap.CompileFilter(filters[i%maxIdx])
+ }
+}
+
+func BenchmarkFilterDecompile(b *testing.B) {
+ b.StopTimer()
+ filters := make([]*ber.Packet, len(testFilters))
+
+ // Test Compiler and Decompiler
+ for idx, i := range testFilters {
+ filters[idx], _ = ldap.CompileFilter(i.filterStr)
+ }
+
+ maxIdx := len(filters)
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ ldap.DecompileFilter(filters[i%maxIdx])
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go
new file mode 100644
index 000000000..1620aaea6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go
@@ -0,0 +1,286 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ldap
+
+import (
+ "errors"
+ "io/ioutil"
+ "os"
+
+ ber "gopkg.in/asn1-ber.v1"
+)
+
+// LDAP Application Codes
+const (
+ ApplicationBindRequest = 0
+ ApplicationBindResponse = 1
+ ApplicationUnbindRequest = 2
+ ApplicationSearchRequest = 3
+ ApplicationSearchResultEntry = 4
+ ApplicationSearchResultDone = 5
+ ApplicationModifyRequest = 6
+ ApplicationModifyResponse = 7
+ ApplicationAddRequest = 8
+ ApplicationAddResponse = 9
+ ApplicationDelRequest = 10
+ ApplicationDelResponse = 11
+ ApplicationModifyDNRequest = 12
+ ApplicationModifyDNResponse = 13
+ ApplicationCompareRequest = 14
+ ApplicationCompareResponse = 15
+ ApplicationAbandonRequest = 16
+ ApplicationSearchResultReference = 19
+ ApplicationExtendedRequest = 23
+ ApplicationExtendedResponse = 24
+)
+
+var ApplicationMap = map[uint8]string{
+ ApplicationBindRequest: "Bind Request",
+ ApplicationBindResponse: "Bind Response",
+ ApplicationUnbindRequest: "Unbind Request",
+ ApplicationSearchRequest: "Search Request",
+ ApplicationSearchResultEntry: "Search Result Entry",
+ ApplicationSearchResultDone: "Search Result Done",
+ ApplicationModifyRequest: "Modify Request",
+ ApplicationModifyResponse: "Modify Response",
+ ApplicationAddRequest: "Add Request",
+ ApplicationAddResponse: "Add Response",
+ ApplicationDelRequest: "Del Request",
+ ApplicationDelResponse: "Del Response",
+ ApplicationModifyDNRequest: "Modify DN Request",
+ ApplicationModifyDNResponse: "Modify DN Response",
+ ApplicationCompareRequest: "Compare Request",
+ ApplicationCompareResponse: "Compare Response",
+ ApplicationAbandonRequest: "Abandon Request",
+ ApplicationSearchResultReference: "Search Result Reference",
+ ApplicationExtendedRequest: "Extended Request",
+ ApplicationExtendedResponse: "Extended Response",
+}
+
+// Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10)
+const (
+ BeheraPasswordExpired = 0
+ BeheraAccountLocked = 1
+ BeheraChangeAfterReset = 2
+ BeheraPasswordModNotAllowed = 3
+ BeheraMustSupplyOldPassword = 4
+ BeheraInsufficientPasswordQuality = 5
+ BeheraPasswordTooShort = 6
+ BeheraPasswordTooYoung = 7
+ BeheraPasswordInHistory = 8
+)
+
+var BeheraPasswordPolicyErrorMap = map[int8]string{
+ BeheraPasswordExpired: "Password expired",
+ BeheraAccountLocked: "Account locked",
+ BeheraChangeAfterReset: "Password must be changed",
+ BeheraPasswordModNotAllowed: "Policy prevents password modification",
+ BeheraMustSupplyOldPassword: "Policy requires old password in order to change password",
+ BeheraInsufficientPasswordQuality: "Password fails quality checks",
+ BeheraPasswordTooShort: "Password is too short for policy",
+ BeheraPasswordTooYoung: "Password has been changed too recently",
+ BeheraPasswordInHistory: "New password is in list of old passwords",
+}
+
+// Adds descriptions to an LDAP Response packet for debugging
+func addLDAPDescriptions(packet *ber.Packet) (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = NewError(ErrorDebugging, errors.New("ldap: cannot process packet to add descriptions"))
+ }
+ }()
+ packet.Description = "LDAP Response"
+ packet.Children[0].Description = "Message ID"
+
+ application := uint8(packet.Children[1].Tag)
+ packet.Children[1].Description = ApplicationMap[application]
+
+ switch application {
+ case ApplicationBindRequest:
+ addRequestDescriptions(packet)
+ case ApplicationBindResponse:
+ addDefaultLDAPResponseDescriptions(packet)
+ case ApplicationUnbindRequest:
+ addRequestDescriptions(packet)
+ case ApplicationSearchRequest:
+ addRequestDescriptions(packet)
+ case ApplicationSearchResultEntry:
+ packet.Children[1].Children[0].Description = "Object Name"
+ packet.Children[1].Children[1].Description = "Attributes"
+ for _, child := range packet.Children[1].Children[1].Children {
+ child.Description = "Attribute"
+ child.Children[0].Description = "Attribute Name"
+ child.Children[1].Description = "Attribute Values"
+ for _, grandchild := range child.Children[1].Children {
+ grandchild.Description = "Attribute Value"
+ }
+ }
+ if len(packet.Children) == 3 {
+ addControlDescriptions(packet.Children[2])
+ }
+ case ApplicationSearchResultDone:
+ addDefaultLDAPResponseDescriptions(packet)
+ case ApplicationModifyRequest:
+ addRequestDescriptions(packet)
+ case ApplicationModifyResponse:
+ case ApplicationAddRequest:
+ addRequestDescriptions(packet)
+ case ApplicationAddResponse:
+ case ApplicationDelRequest:
+ addRequestDescriptions(packet)
+ case ApplicationDelResponse:
+ case ApplicationModifyDNRequest:
+ addRequestDescriptions(packet)
+ case ApplicationModifyDNResponse:
+ case ApplicationCompareRequest:
+ addRequestDescriptions(packet)
+ case ApplicationCompareResponse:
+ case ApplicationAbandonRequest:
+ addRequestDescriptions(packet)
+ case ApplicationSearchResultReference:
+ case ApplicationExtendedRequest:
+ addRequestDescriptions(packet)
+ case ApplicationExtendedResponse:
+ }
+
+ return nil
+}
+
+func addControlDescriptions(packet *ber.Packet) {
+ packet.Description = "Controls"
+ for _, child := range packet.Children {
+ child.Description = "Control"
+ child.Children[0].Description = "Control Type (" + ControlTypeMap[child.Children[0].Value.(string)] + ")"
+ value := child.Children[1]
+ if len(child.Children) == 3 {
+ child.Children[1].Description = "Criticality"
+ value = child.Children[2]
+ }
+ value.Description = "Control Value"
+
+ switch child.Children[0].Value.(string) {
+ case ControlTypePaging:
+ value.Description += " (Paging)"
+ if value.Value != nil {
+ valueChildren := ber.DecodePacket(value.Data.Bytes())
+ value.Data.Truncate(0)
+ value.Value = nil
+ valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes()
+ value.AppendChild(valueChildren)
+ }
+ value.Children[0].Description = "Real Search Control Value"
+ value.Children[0].Children[0].Description = "Paging Size"
+ value.Children[0].Children[1].Description = "Cookie"
+
+ case ControlTypeBeheraPasswordPolicy:
+ value.Description += " (Password Policy - Behera Draft)"
+ if value.Value != nil {
+ valueChildren := ber.DecodePacket(value.Data.Bytes())
+ value.Data.Truncate(0)
+ value.Value = nil
+ value.AppendChild(valueChildren)
+ }
+ sequence := value.Children[0]
+ for _, child := range sequence.Children {
+ if child.Tag == 0 {
+ //Warning
+ child := child.Children[0]
+ packet := ber.DecodePacket(child.Data.Bytes())
+ val, ok := packet.Value.(int64)
+ if ok {
+ if child.Tag == 0 {
+ //timeBeforeExpiration
+ value.Description += " (TimeBeforeExpiration)"
+ child.Value = val
+ } else if child.Tag == 1 {
+ //graceAuthNsRemaining
+ value.Description += " (GraceAuthNsRemaining)"
+ child.Value = val
+ }
+ }
+ } else if child.Tag == 1 {
+ // Error
+ packet := ber.DecodePacket(child.Data.Bytes())
+ val, ok := packet.Value.(int8)
+ if !ok {
+ val = -1
+ }
+ child.Description = "Error"
+ child.Value = val
+ }
+ }
+ }
+ }
+}
+
+func addRequestDescriptions(packet *ber.Packet) {
+ packet.Description = "LDAP Request"
+ packet.Children[0].Description = "Message ID"
+ packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)]
+ if len(packet.Children) == 3 {
+ addControlDescriptions(packet.Children[2])
+ }
+}
+
+func addDefaultLDAPResponseDescriptions(packet *ber.Packet) {
+ resultCode, _ := getLDAPResultCode(packet)
+ packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[resultCode] + ")"
+ packet.Children[1].Children[1].Description = "Matched DN"
+ packet.Children[1].Children[2].Description = "Error Message"
+ if len(packet.Children[1].Children) > 3 {
+ packet.Children[1].Children[3].Description = "Referral"
+ }
+ if len(packet.Children) == 3 {
+ addControlDescriptions(packet.Children[2])
+ }
+}
+
+func DebugBinaryFile(fileName string) error {
+ file, err := ioutil.ReadFile(fileName)
+ if err != nil {
+ return NewError(ErrorDebugging, err)
+ }
+ ber.PrintBytes(os.Stdout, file, "")
+ packet := ber.DecodePacket(file)
+ addLDAPDescriptions(packet)
+ ber.PrintPacket(packet)
+
+ return nil
+}
+
+var hex = "0123456789abcdef"
+
+func mustEscape(c byte) bool {
+ return c > 0x7f || c == '(' || c == ')' || c == '\\' || c == '*' || c == 0
+}
+
+// EscapeFilter escapes from the provided LDAP filter string the special
+// characters in the set `()*\` and those out of the range 0 < c < 0x80,
+// as defined in RFC4515.
+func EscapeFilter(filter string) string {
+ escape := 0
+ for i := 0; i < len(filter); i++ {
+ if mustEscape(filter[i]) {
+ escape++
+ }
+ }
+ if escape == 0 {
+ return filter
+ }
+ buf := make([]byte, len(filter)+escape*2)
+ for i, j := 0, 0; i < len(filter); i++ {
+ c := filter[i]
+ if mustEscape(c) {
+ buf[j+0] = '\\'
+ buf[j+1] = hex[c>>4]
+ buf[j+2] = hex[c&0xf]
+ j += 3
+ } else {
+ buf[j] = c
+ j++
+ }
+ }
+ return string(buf)
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go
new file mode 100644
index 000000000..63292747f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go
@@ -0,0 +1,249 @@
+package ldap_test
+
+import (
+ "crypto/tls"
+ "fmt"
+ "testing"
+
+ "gopkg.in/ldap.v2"
+)
+
+var ldapServer = "ldap.itd.umich.edu"
+var ldapPort = uint16(389)
+var ldapTLSPort = uint16(636)
+var baseDN = "dc=umich,dc=edu"
+var filter = []string{
+ "(cn=cis-fac)",
+ "(&(owner=*)(cn=cis-fac))",
+ "(&(objectclass=rfc822mailgroup)(cn=*Computer*))",
+ "(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"}
+var attributes = []string{
+ "cn",
+ "description"}
+
+func TestDial(t *testing.T) {
+ fmt.Printf("TestDial: starting...\n")
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+ defer l.Close()
+ fmt.Printf("TestDial: finished...\n")
+}
+
+func TestDialTLS(t *testing.T) {
+ fmt.Printf("TestDialTLS: starting...\n")
+ l, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapTLSPort), &tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+ defer l.Close()
+ fmt.Printf("TestDialTLS: finished...\n")
+}
+
+func TestStartTLS(t *testing.T) {
+ fmt.Printf("TestStartTLS: starting...\n")
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+ err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+ fmt.Printf("TestStartTLS: finished...\n")
+}
+
+func TestSearch(t *testing.T) {
+ fmt.Printf("TestSearch: starting...\n")
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+ defer l.Close()
+
+ searchRequest := ldap.NewSearchRequest(
+ baseDN,
+ ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
+ filter[0],
+ attributes,
+ nil)
+
+ sr, err := l.Search(searchRequest)
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+
+ fmt.Printf("TestSearch: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
+}
+
+func TestSearchStartTLS(t *testing.T) {
+ fmt.Printf("TestSearchStartTLS: starting...\n")
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+ defer l.Close()
+
+ searchRequest := ldap.NewSearchRequest(
+ baseDN,
+ ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
+ filter[0],
+ attributes,
+ nil)
+
+ sr, err := l.Search(searchRequest)
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+
+ fmt.Printf("TestSearchStartTLS: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
+
+ fmt.Printf("TestSearchStartTLS: upgrading with startTLS\n")
+ err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+
+ sr, err = l.Search(searchRequest)
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+
+ fmt.Printf("TestSearchStartTLS: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
+}
+
+func TestSearchWithPaging(t *testing.T) {
+ fmt.Printf("TestSearchWithPaging: starting...\n")
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+ defer l.Close()
+
+ err = l.Bind("", "")
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+
+ searchRequest := ldap.NewSearchRequest(
+ baseDN,
+ ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
+ filter[2],
+ attributes,
+ nil)
+ sr, err := l.SearchWithPaging(searchRequest, 5)
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+
+ fmt.Printf("TestSearchWithPaging: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
+}
+
+func searchGoroutine(t *testing.T, l *ldap.Conn, results chan *ldap.SearchResult, i int) {
+ searchRequest := ldap.NewSearchRequest(
+ baseDN,
+ ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
+ filter[i],
+ attributes,
+ nil)
+ sr, err := l.Search(searchRequest)
+ if err != nil {
+ t.Errorf(err.Error())
+ results <- nil
+ return
+ }
+ results <- sr
+}
+
+func testMultiGoroutineSearch(t *testing.T, TLS bool, startTLS bool) {
+ fmt.Printf("TestMultiGoroutineSearch: starting...\n")
+ var l *ldap.Conn
+ var err error
+ if TLS {
+ l, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapTLSPort), &tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+ defer l.Close()
+ } else {
+ l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+ if startTLS {
+ fmt.Printf("TestMultiGoroutineSearch: using StartTLS...\n")
+ err := l.StartTLS(&tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+
+ }
+ }
+
+ results := make([]chan *ldap.SearchResult, len(filter))
+ for i := range filter {
+ results[i] = make(chan *ldap.SearchResult)
+ go searchGoroutine(t, l, results[i], i)
+ }
+ for i := range filter {
+ sr := <-results[i]
+ if sr == nil {
+ t.Errorf("Did not receive results from goroutine for %q", filter[i])
+ } else {
+ fmt.Printf("TestMultiGoroutineSearch(%d): %s -> num of entries = %d\n", i, filter[i], len(sr.Entries))
+ }
+ }
+}
+
+func TestMultiGoroutineSearch(t *testing.T) {
+ testMultiGoroutineSearch(t, false, false)
+ testMultiGoroutineSearch(t, true, true)
+ testMultiGoroutineSearch(t, false, true)
+}
+
+func TestEscapeFilter(t *testing.T) {
+ if got, want := ldap.EscapeFilter("a\x00b(c)d*e\\f"), `a\00b\28c\29d\2ae\5cf`; got != want {
+ t.Errorf("Got %s, expected %s", want, got)
+ }
+ if got, want := ldap.EscapeFilter("LuÄić"), `Lu\c4\8di\c4\87`; got != want {
+ t.Errorf("Got %s, expected %s", want, got)
+ }
+}
+
+func TestCompare(t *testing.T) {
+ fmt.Printf("TestCompare: starting...\n")
+ l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+ defer l.Close()
+
+ dn := "cn=math mich,ou=User Groups,ou=Groups,dc=umich,dc=edu"
+ attribute := "cn"
+ value := "math mich"
+
+ sr, err := l.Compare(dn, attribute, value)
+ if err != nil {
+ t.Errorf(err.Error())
+ return
+ }
+
+ fmt.Printf("TestCompare: -> %v\n", sr)
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/modify.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/modify.go
new file mode 100644
index 000000000..4372a19dc
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/modify.go
@@ -0,0 +1,156 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+// File contains Modify functionality
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
+// object LDAPDN,
+// changes SEQUENCE OF change SEQUENCE {
+// operation ENUMERATED {
+// add (0),
+// delete (1),
+// replace (2),
+// ... },
+// modification PartialAttribute } }
+//
+// PartialAttribute ::= SEQUENCE {
+// type AttributeDescription,
+// vals SET OF value AttributeValue }
+//
+// AttributeDescription ::= LDAPString
+// -- Constrained to <attributedescription>
+// -- [RFC4512]
+//
+// AttributeValue ::= OCTET STRING
+//
+
+package ldap
+
+import (
+ "errors"
+ "log"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+const (
+ AddAttribute = 0
+ DeleteAttribute = 1
+ ReplaceAttribute = 2
+)
+
+type PartialAttribute struct {
+ attrType string
+ attrVals []string
+}
+
+func (p *PartialAttribute) encode() *ber.Packet {
+ seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute")
+ seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.attrType, "Type"))
+ set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
+ for _, value := range p.attrVals {
+ set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
+ }
+ seq.AppendChild(set)
+ return seq
+}
+
+type ModifyRequest struct {
+ dn string
+ addAttributes []PartialAttribute
+ deleteAttributes []PartialAttribute
+ replaceAttributes []PartialAttribute
+}
+
+func (m *ModifyRequest) Add(attrType string, attrVals []string) {
+ m.addAttributes = append(m.addAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals})
+}
+
+func (m *ModifyRequest) Delete(attrType string, attrVals []string) {
+ m.deleteAttributes = append(m.deleteAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals})
+}
+
+func (m *ModifyRequest) Replace(attrType string, attrVals []string) {
+ m.replaceAttributes = append(m.replaceAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals})
+}
+
+func (m ModifyRequest) encode() *ber.Packet {
+ request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request")
+ request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.dn, "DN"))
+ changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes")
+ for _, attribute := range m.addAttributes {
+ change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
+ change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(AddAttribute), "Operation"))
+ change.AppendChild(attribute.encode())
+ changes.AppendChild(change)
+ }
+ for _, attribute := range m.deleteAttributes {
+ change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
+ change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(DeleteAttribute), "Operation"))
+ change.AppendChild(attribute.encode())
+ changes.AppendChild(change)
+ }
+ for _, attribute := range m.replaceAttributes {
+ change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
+ change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(ReplaceAttribute), "Operation"))
+ change.AppendChild(attribute.encode())
+ changes.AppendChild(change)
+ }
+ request.AppendChild(changes)
+ return request
+}
+
+func NewModifyRequest(
+ dn string,
+) *ModifyRequest {
+ return &ModifyRequest{
+ dn: dn,
+ }
+}
+
+func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
+ messageID := l.nextMessageID()
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+ packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+ packet.AppendChild(modifyRequest.encode())
+
+ l.Debug.PrintPacket(packet)
+
+ channel, err := l.sendMessage(packet)
+ if err != nil {
+ return err
+ }
+ if channel == nil {
+ return NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+ }
+ defer l.finishMessage(messageID)
+
+ l.Debug.Printf("%d: waiting for response", messageID)
+ packet = <-channel
+ l.Debug.Printf("%d: got response %p", messageID, packet)
+ if packet == nil {
+ return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
+ }
+
+ if l.Debug {
+ if err := addLDAPDescriptions(packet); err != nil {
+ return err
+ }
+ ber.PrintPacket(packet)
+ }
+
+ if packet.Children[1].Tag == ApplicationModifyResponse {
+ resultCode, resultDescription := getLDAPResultCode(packet)
+ if resultCode != 0 {
+ return NewError(resultCode, errors.New(resultDescription))
+ }
+ } else {
+ log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
+ }
+
+ l.Debug.Printf("%d: returning", messageID)
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/passwdmodify.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/passwdmodify.go
new file mode 100644
index 000000000..508b11ed7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/passwdmodify.go
@@ -0,0 +1,137 @@
+// This file contains the password modify extended operation as specified in rfc 3062
+//
+// https://tools.ietf.org/html/rfc3062
+//
+
+package ldap
+
+import (
+ "errors"
+ "fmt"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+const (
+ passwordModifyOID = "1.3.6.1.4.1.4203.1.11.1"
+)
+
+type PasswordModifyRequest struct {
+ UserIdentity string
+ OldPassword string
+ NewPassword string
+}
+
+type PasswordModifyResult struct {
+ GeneratedPassword string
+}
+
+func (r *PasswordModifyRequest) encode() (*ber.Packet, error) {
+ request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Password Modify Extended Operation")
+ request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, passwordModifyOID, "Extended Request Name: Password Modify OID"))
+ extendedRequestValue := ber.Encode(ber.ClassContext, ber.TypePrimitive, 1, nil, "Extended Request Value: Password Modify Request")
+ passwordModifyRequestValue := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Password Modify Request")
+ if r.UserIdentity != "" {
+ passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, r.UserIdentity, "User Identity"))
+ }
+ if r.OldPassword != "" {
+ passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 1, r.OldPassword, "Old Password"))
+ }
+ if r.NewPassword != "" {
+ passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 2, r.NewPassword, "New Password"))
+ }
+
+ extendedRequestValue.AppendChild(passwordModifyRequestValue)
+ request.AppendChild(extendedRequestValue)
+
+ return request, nil
+}
+
+// Create a new PasswordModifyRequest
+//
+// According to the RFC 3602:
+// userIdentity is a string representing the user associated with the request.
+// This string may or may not be an LDAPDN (RFC 2253).
+// If userIdentity is empty then the operation will act on the user associated
+// with the session.
+//
+// oldPassword is the current user's password, it can be empty or it can be
+// needed depending on the session user access rights (usually an administrator
+// can change a user's password without knowing the current one) and the
+// password policy (see pwdSafeModify password policy's attribute)
+//
+// newPassword is the desired user's password. If empty the server can return
+// an error or generate a new password that will be available in the
+// PasswordModifyResult.GeneratedPassword
+//
+func NewPasswordModifyRequest(userIdentity string, oldPassword string, newPassword string) *PasswordModifyRequest {
+ return &PasswordModifyRequest{
+ UserIdentity: userIdentity,
+ OldPassword: oldPassword,
+ NewPassword: newPassword,
+ }
+}
+
+func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) {
+ messageID := l.nextMessageID()
+
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+ packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+
+ encodedPasswordModifyRequest, err := passwordModifyRequest.encode()
+ if err != nil {
+ return nil, err
+ }
+ packet.AppendChild(encodedPasswordModifyRequest)
+
+ l.Debug.PrintPacket(packet)
+
+ channel, err := l.sendMessage(packet)
+ if err != nil {
+ return nil, err
+ }
+ if channel == nil {
+ return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+ }
+ defer l.finishMessage(messageID)
+
+ result := &PasswordModifyResult{}
+
+ l.Debug.Printf("%d: waiting for response", messageID)
+ packet = <-channel
+ l.Debug.Printf("%d: got response %p", messageID, packet)
+
+ 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 {
+ passwordModifyReponseValue := ber.DecodePacket(child.Data.Bytes())
+ if len(passwordModifyReponseValue.Children) == 1 {
+ if passwordModifyReponseValue.Children[0].Tag == 0 {
+ result.GeneratedPassword = ber.DecodeString(passwordModifyReponseValue.Children[0].Data.Bytes())
+ }
+ }
+ }
+ }
+
+ return result, nil
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/search.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/search.go
new file mode 100644
index 000000000..f63c9fb02
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/search.go
@@ -0,0 +1,403 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+// File contains Search functionality
+//
+// https://tools.ietf.org/html/rfc4511
+//
+// SearchRequest ::= [APPLICATION 3] SEQUENCE {
+// baseObject LDAPDN,
+// scope ENUMERATED {
+// baseObject (0),
+// singleLevel (1),
+// wholeSubtree (2),
+// ... },
+// derefAliases ENUMERATED {
+// neverDerefAliases (0),
+// derefInSearching (1),
+// derefFindingBaseObj (2),
+// derefAlways (3) },
+// sizeLimit INTEGER (0 .. maxInt),
+// timeLimit INTEGER (0 .. maxInt),
+// typesOnly BOOLEAN,
+// filter Filter,
+// attributes AttributeSelection }
+//
+// AttributeSelection ::= SEQUENCE OF selector LDAPString
+// -- The LDAPString is constrained to
+// -- <attributeSelector> in Section 4.5.1.8
+//
+// Filter ::= CHOICE {
+// and [0] SET SIZE (1..MAX) OF filter Filter,
+// or [1] SET SIZE (1..MAX) OF filter Filter,
+// not [2] Filter,
+// equalityMatch [3] AttributeValueAssertion,
+// substrings [4] SubstringFilter,
+// greaterOrEqual [5] AttributeValueAssertion,
+// lessOrEqual [6] AttributeValueAssertion,
+// present [7] AttributeDescription,
+// approxMatch [8] AttributeValueAssertion,
+// extensibleMatch [9] MatchingRuleAssertion,
+// ... }
+//
+// SubstringFilter ::= SEQUENCE {
+// type AttributeDescription,
+// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
+// initial [0] AssertionValue, -- can occur at most once
+// any [1] AssertionValue,
+// final [2] AssertionValue } -- can occur at most once
+// }
+//
+// MatchingRuleAssertion ::= SEQUENCE {
+// matchingRule [1] MatchingRuleId OPTIONAL,
+// type [2] AttributeDescription OPTIONAL,
+// matchValue [3] AssertionValue,
+// dnAttributes [4] BOOLEAN DEFAULT FALSE }
+//
+//
+
+package ldap
+
+import (
+ "errors"
+ "fmt"
+ "sort"
+ "strings"
+
+ "gopkg.in/asn1-ber.v1"
+)
+
+const (
+ ScopeBaseObject = 0
+ ScopeSingleLevel = 1
+ ScopeWholeSubtree = 2
+)
+
+var ScopeMap = map[int]string{
+ ScopeBaseObject: "Base Object",
+ ScopeSingleLevel: "Single Level",
+ ScopeWholeSubtree: "Whole Subtree",
+}
+
+const (
+ NeverDerefAliases = 0
+ DerefInSearching = 1
+ DerefFindingBaseObj = 2
+ DerefAlways = 3
+)
+
+var DerefMap = map[int]string{
+ NeverDerefAliases: "NeverDerefAliases",
+ DerefInSearching: "DerefInSearching",
+ DerefFindingBaseObj: "DerefFindingBaseObj",
+ DerefAlways: "DerefAlways",
+}
+
+// NewEntry returns an Entry object with the specified distinguished name and attribute key-value pairs.
+// The map of attributes is accessed in alphabetical order of the keys in order to ensure that, for the
+// same input map of attributes, the output entry will contain the same order of attributes
+func NewEntry(dn string, attributes map[string][]string) *Entry {
+ var attributeNames []string
+ for attributeName := range attributes {
+ attributeNames = append(attributeNames, attributeName)
+ }
+ sort.Strings(attributeNames)
+
+ var encodedAttributes []*EntryAttribute
+ for _, attributeName := range attributeNames {
+ encodedAttributes = append(encodedAttributes, NewEntryAttribute(attributeName, attributes[attributeName]))
+ }
+ return &Entry{
+ DN: dn,
+ Attributes: encodedAttributes,
+ }
+}
+
+type Entry struct {
+ DN string
+ Attributes []*EntryAttribute
+}
+
+func (e *Entry) GetAttributeValues(attribute string) []string {
+ for _, attr := range e.Attributes {
+ if attr.Name == attribute {
+ return attr.Values
+ }
+ }
+ return []string{}
+}
+
+func (e *Entry) GetRawAttributeValues(attribute string) [][]byte {
+ for _, attr := range e.Attributes {
+ if attr.Name == attribute {
+ return attr.ByteValues
+ }
+ }
+ return [][]byte{}
+}
+
+func (e *Entry) GetAttributeValue(attribute string) string {
+ values := e.GetAttributeValues(attribute)
+ if len(values) == 0 {
+ return ""
+ }
+ return values[0]
+}
+
+func (e *Entry) GetRawAttributeValue(attribute string) []byte {
+ values := e.GetRawAttributeValues(attribute)
+ if len(values) == 0 {
+ return []byte{}
+ }
+ return values[0]
+}
+
+func (e *Entry) Print() {
+ fmt.Printf("DN: %s\n", e.DN)
+ for _, attr := range e.Attributes {
+ attr.Print()
+ }
+}
+
+func (e *Entry) PrettyPrint(indent int) {
+ fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN)
+ for _, attr := range e.Attributes {
+ attr.PrettyPrint(indent + 2)
+ }
+}
+
+// NewEntryAttribute returns a new EntryAttribute with the desired key-value pair
+func NewEntryAttribute(name string, values []string) *EntryAttribute {
+ var bytes [][]byte
+ for _, value := range values {
+ bytes = append(bytes, []byte(value))
+ }
+ return &EntryAttribute{
+ Name: name,
+ Values: values,
+ ByteValues: bytes,
+ }
+}
+
+type EntryAttribute struct {
+ Name string
+ Values []string
+ ByteValues [][]byte
+}
+
+func (e *EntryAttribute) Print() {
+ fmt.Printf("%s: %s\n", e.Name, e.Values)
+}
+
+func (e *EntryAttribute) PrettyPrint(indent int) {
+ fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values)
+}
+
+type SearchResult struct {
+ Entries []*Entry
+ Referrals []string
+ Controls []Control
+}
+
+func (s *SearchResult) Print() {
+ for _, entry := range s.Entries {
+ entry.Print()
+ }
+}
+
+func (s *SearchResult) PrettyPrint(indent int) {
+ for _, entry := range s.Entries {
+ entry.PrettyPrint(indent)
+ }
+}
+
+type SearchRequest struct {
+ BaseDN string
+ Scope int
+ DerefAliases int
+ SizeLimit int
+ TimeLimit int
+ TypesOnly bool
+ Filter string
+ Attributes []string
+ Controls []Control
+}
+
+func (s *SearchRequest) encode() (*ber.Packet, error) {
+ request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request")
+ request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, s.BaseDN, "Base DN"))
+ request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.Scope), "Scope"))
+ request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.DerefAliases), "Deref Aliases"))
+ request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.SizeLimit), "Size Limit"))
+ request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.TimeLimit), "Time Limit"))
+ request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, s.TypesOnly, "Types Only"))
+ // compile and encode filter
+ filterPacket, err := CompileFilter(s.Filter)
+ if err != nil {
+ return nil, err
+ }
+ request.AppendChild(filterPacket)
+ // encode attributes
+ attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
+ for _, attribute := range s.Attributes {
+ attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
+ }
+ request.AppendChild(attributesPacket)
+ return request, nil
+}
+
+func NewSearchRequest(
+ BaseDN string,
+ Scope, DerefAliases, SizeLimit, TimeLimit int,
+ TypesOnly bool,
+ Filter string,
+ Attributes []string,
+ Controls []Control,
+) *SearchRequest {
+ return &SearchRequest{
+ BaseDN: BaseDN,
+ Scope: Scope,
+ DerefAliases: DerefAliases,
+ SizeLimit: SizeLimit,
+ TimeLimit: TimeLimit,
+ TypesOnly: TypesOnly,
+ Filter: Filter,
+ Attributes: Attributes,
+ Controls: Controls,
+ }
+}
+
+func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
+ if searchRequest.Controls == nil {
+ searchRequest.Controls = make([]Control, 0)
+ }
+
+ pagingControl := NewControlPaging(pagingSize)
+ searchRequest.Controls = append(searchRequest.Controls, pagingControl)
+ searchResult := new(SearchResult)
+ for {
+ result, err := l.Search(searchRequest)
+ l.Debug.Printf("Looking for Paging Control...")
+ if err != nil {
+ return searchResult, err
+ }
+ if result == nil {
+ return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
+ }
+
+ for _, entry := range result.Entries {
+ searchResult.Entries = append(searchResult.Entries, entry)
+ }
+ for _, referral := range result.Referrals {
+ searchResult.Referrals = append(searchResult.Referrals, referral)
+ }
+ for _, control := range result.Controls {
+ searchResult.Controls = append(searchResult.Controls, control)
+ }
+
+ l.Debug.Printf("Looking for Paging Control...")
+ pagingResult := FindControl(result.Controls, ControlTypePaging)
+ if pagingResult == nil {
+ pagingControl = nil
+ l.Debug.Printf("Could not find paging control. Breaking...")
+ break
+ }
+
+ cookie := pagingResult.(*ControlPaging).Cookie
+ if len(cookie) == 0 {
+ pagingControl = nil
+ l.Debug.Printf("Could not find cookie. Breaking...")
+ break
+ }
+ pagingControl.SetCookie(cookie)
+ }
+
+ if pagingControl != nil {
+ l.Debug.Printf("Abandoning Paging...")
+ pagingControl.PagingSize = 0
+ l.Search(searchRequest)
+ }
+
+ return searchResult, nil
+}
+
+func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
+ messageID := l.nextMessageID()
+ packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
+ packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
+ // encode search request
+ encodedSearchRequest, err := searchRequest.encode()
+ if err != nil {
+ return nil, err
+ }
+ packet.AppendChild(encodedSearchRequest)
+ // encode search controls
+ if searchRequest.Controls != nil {
+ packet.AppendChild(encodeControls(searchRequest.Controls))
+ }
+
+ l.Debug.PrintPacket(packet)
+
+ channel, err := l.sendMessage(packet)
+ if err != nil {
+ return nil, err
+ }
+ if channel == nil {
+ return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message"))
+ }
+ defer l.finishMessage(messageID)
+
+ result := &SearchResult{
+ Entries: make([]*Entry, 0),
+ Referrals: make([]string, 0),
+ Controls: make([]Control, 0)}
+
+ foundSearchResultDone := false
+ for !foundSearchResultDone {
+ l.Debug.Printf("%d: waiting for response", messageID)
+ packet = <-channel
+ l.Debug.Printf("%d: got response %p", messageID, packet)
+ 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)
+ }
+
+ switch packet.Children[1].Tag {
+ case 4:
+ entry := new(Entry)
+ entry.DN = packet.Children[1].Children[0].Value.(string)
+ for _, child := range packet.Children[1].Children[1].Children {
+ attr := new(EntryAttribute)
+ attr.Name = child.Children[0].Value.(string)
+ for _, value := range child.Children[1].Children {
+ attr.Values = append(attr.Values, value.Value.(string))
+ attr.ByteValues = append(attr.ByteValues, value.ByteValue)
+ }
+ entry.Attributes = append(entry.Attributes, attr)
+ }
+ result.Entries = append(result.Entries, entry)
+ case 5:
+ resultCode, resultDescription := getLDAPResultCode(packet)
+ if resultCode != 0 {
+ return result, NewError(resultCode, errors.New(resultDescription))
+ }
+ if len(packet.Children) == 3 {
+ for _, child := range packet.Children[2].Children {
+ result.Controls = append(result.Controls, DecodeControl(child))
+ }
+ }
+ foundSearchResultDone = true
+ case 19:
+ result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string))
+ }
+ }
+ l.Debug.Printf("%d: returning", messageID)
+ return result, nil
+}
diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/search_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/search_test.go
new file mode 100644
index 000000000..efb8147d1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/search_test.go
@@ -0,0 +1,31 @@
+package ldap
+
+import (
+ "reflect"
+ "testing"
+)
+
+// TestNewEntry tests that repeated calls to NewEntry return the same value with the same input
+func TestNewEntry(t *testing.T) {
+ dn := "testDN"
+ attributes := map[string][]string{
+ "alpha": {"value"},
+ "beta": {"value"},
+ "gamma": {"value"},
+ "delta": {"value"},
+ "epsilon": {"value"},
+ }
+ exectedEntry := NewEntry(dn, attributes)
+
+ iteration := 0
+ for {
+ if iteration == 100 {
+ break
+ }
+ testEntry := NewEntry(dn, attributes)
+ if !reflect.DeepEqual(exectedEntry, testEntry) {
+ t.Fatalf("consequent calls to NewEntry did not yield the same result:\n\texpected:\n\t%s\n\tgot:\n\t%s\n", exectedEntry, testEntry)
+ }
+ iteration = iteration + 1
+ }
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml
new file mode 100644
index 000000000..44aa48b87
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml
@@ -0,0 +1,15 @@
+language: go
+go:
+ - 1.2
+ - 1.3
+ - 1.4
+ - 1.5
+ - tip
+go_import_path: gopkg.in/asn-ber.v1
+install:
+ - go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v
+ - go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v
+ - go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover
+ - go build -v ./...
+script:
+ - go test -v -cover ./...
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE
new file mode 100644
index 000000000..744875676
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md
new file mode 100644
index 000000000..e3a9560d6
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md
@@ -0,0 +1,24 @@
+[![GoDoc](https://godoc.org/gopkg.in/asn1-ber.v1?status.svg)](https://godoc.org/gopkg.in/asn1-ber.v1) [![Build Status](https://travis-ci.org/go-asn1-ber/asn1-ber.svg)](https://travis-ci.org/go-asn1-ber/asn1-ber)
+
+
+ASN1 BER Encoding / Decoding Library for the GO programming language.
+---------------------------------------------------------------------
+
+Required libraries:
+ None
+
+Working:
+ Very basic encoding / decoding needed for LDAP protocol
+
+Tests Implemented:
+ A few
+
+TODO:
+ Fix all encoding / decoding to conform to ASN1 BER spec
+ Implement Tests / Benchmarks
+
+---
+
+The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
+The design is licensed under the Creative Commons 3.0 Attributions license.
+Read this article for more details: http://blog.golang.org/gopher
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go
new file mode 100644
index 000000000..25cc921be
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go
@@ -0,0 +1,504 @@
+package ber
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+)
+
+type Packet struct {
+ Identifier
+ Value interface{}
+ ByteValue []byte
+ Data *bytes.Buffer
+ Children []*Packet
+ Description string
+}
+
+type Identifier struct {
+ ClassType Class
+ TagType Type
+ Tag Tag
+}
+
+type Tag uint64
+
+const (
+ TagEOC Tag = 0x00
+ TagBoolean Tag = 0x01
+ TagInteger Tag = 0x02
+ TagBitString Tag = 0x03
+ TagOctetString Tag = 0x04
+ TagNULL Tag = 0x05
+ TagObjectIdentifier Tag = 0x06
+ TagObjectDescriptor Tag = 0x07
+ TagExternal Tag = 0x08
+ TagRealFloat Tag = 0x09
+ TagEnumerated Tag = 0x0a
+ TagEmbeddedPDV Tag = 0x0b
+ TagUTF8String Tag = 0x0c
+ TagRelativeOID Tag = 0x0d
+ TagSequence Tag = 0x10
+ TagSet Tag = 0x11
+ TagNumericString Tag = 0x12
+ TagPrintableString Tag = 0x13
+ TagT61String Tag = 0x14
+ TagVideotexString Tag = 0x15
+ TagIA5String Tag = 0x16
+ TagUTCTime Tag = 0x17
+ TagGeneralizedTime Tag = 0x18
+ TagGraphicString Tag = 0x19
+ TagVisibleString Tag = 0x1a
+ TagGeneralString Tag = 0x1b
+ TagUniversalString Tag = 0x1c
+ TagCharacterString Tag = 0x1d
+ TagBMPString Tag = 0x1e
+ TagBitmask Tag = 0x1f // xxx11111b
+
+ // HighTag indicates the start of a high-tag byte sequence
+ HighTag Tag = 0x1f // xxx11111b
+ // HighTagContinueBitmask indicates the high-tag byte sequence should continue
+ HighTagContinueBitmask Tag = 0x80 // 10000000b
+ // HighTagValueBitmask obtains the tag value from a high-tag byte sequence byte
+ HighTagValueBitmask Tag = 0x7f // 01111111b
+)
+
+const (
+ // LengthLongFormBitmask is the mask to apply to the length byte to see if a long-form byte sequence is used
+ LengthLongFormBitmask = 0x80
+ // LengthValueBitmask is the mask to apply to the length byte to get the number of bytes in the long-form byte sequence
+ LengthValueBitmask = 0x7f
+
+ // LengthIndefinite is returned from readLength to indicate an indefinite length
+ LengthIndefinite = -1
+)
+
+var tagMap = map[Tag]string{
+ TagEOC: "EOC (End-of-Content)",
+ TagBoolean: "Boolean",
+ TagInteger: "Integer",
+ TagBitString: "Bit String",
+ TagOctetString: "Octet String",
+ TagNULL: "NULL",
+ TagObjectIdentifier: "Object Identifier",
+ TagObjectDescriptor: "Object Descriptor",
+ TagExternal: "External",
+ TagRealFloat: "Real (float)",
+ TagEnumerated: "Enumerated",
+ TagEmbeddedPDV: "Embedded PDV",
+ TagUTF8String: "UTF8 String",
+ TagRelativeOID: "Relative-OID",
+ TagSequence: "Sequence and Sequence of",
+ TagSet: "Set and Set OF",
+ TagNumericString: "Numeric String",
+ TagPrintableString: "Printable String",
+ TagT61String: "T61 String",
+ TagVideotexString: "Videotex String",
+ TagIA5String: "IA5 String",
+ TagUTCTime: "UTC Time",
+ TagGeneralizedTime: "Generalized Time",
+ TagGraphicString: "Graphic String",
+ TagVisibleString: "Visible String",
+ TagGeneralString: "General String",
+ TagUniversalString: "Universal String",
+ TagCharacterString: "Character String",
+ TagBMPString: "BMP String",
+}
+
+type Class uint8
+
+const (
+ ClassUniversal Class = 0 // 00xxxxxxb
+ ClassApplication Class = 64 // 01xxxxxxb
+ ClassContext Class = 128 // 10xxxxxxb
+ ClassPrivate Class = 192 // 11xxxxxxb
+ ClassBitmask Class = 192 // 11xxxxxxb
+)
+
+var ClassMap = map[Class]string{
+ ClassUniversal: "Universal",
+ ClassApplication: "Application",
+ ClassContext: "Context",
+ ClassPrivate: "Private",
+}
+
+type Type uint8
+
+const (
+ TypePrimitive Type = 0 // xx0xxxxxb
+ TypeConstructed Type = 32 // xx1xxxxxb
+ TypeBitmask Type = 32 // xx1xxxxxb
+)
+
+var TypeMap = map[Type]string{
+ TypePrimitive: "Primitive",
+ TypeConstructed: "Constructed",
+}
+
+var Debug bool = false
+
+func PrintBytes(out io.Writer, buf []byte, indent string) {
+ data_lines := make([]string, (len(buf)/30)+1)
+ num_lines := make([]string, (len(buf)/30)+1)
+
+ for i, b := range buf {
+ data_lines[i/30] += fmt.Sprintf("%02x ", b)
+ num_lines[i/30] += fmt.Sprintf("%02d ", (i+1)%100)
+ }
+
+ for i := 0; i < len(data_lines); i++ {
+ out.Write([]byte(indent + data_lines[i] + "\n"))
+ out.Write([]byte(indent + num_lines[i] + "\n\n"))
+ }
+}
+
+func PrintPacket(p *Packet) {
+ printPacket(os.Stdout, p, 0, false)
+}
+
+func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) {
+ indent_str := ""
+
+ for len(indent_str) != indent {
+ indent_str += " "
+ }
+
+ class_str := ClassMap[p.ClassType]
+
+ tagtype_str := TypeMap[p.TagType]
+
+ tag_str := fmt.Sprintf("0x%02X", p.Tag)
+
+ if p.ClassType == ClassUniversal {
+ tag_str = tagMap[p.Tag]
+ }
+
+ value := fmt.Sprint(p.Value)
+ description := ""
+
+ if p.Description != "" {
+ description = p.Description + ": "
+ }
+
+ fmt.Fprintf(out, "%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value)
+
+ if printBytes {
+ PrintBytes(out, p.Bytes(), indent_str)
+ }
+
+ for _, child := range p.Children {
+ printPacket(out, child, indent+1, printBytes)
+ }
+}
+
+// ReadPacket reads a single Packet from the reader
+func ReadPacket(reader io.Reader) (*Packet, error) {
+ p, _, err := readPacket(reader)
+ if err != nil {
+ return nil, err
+ }
+ return p, nil
+}
+
+func DecodeString(data []byte) string {
+ return string(data)
+}
+
+func parseInt64(bytes []byte) (ret int64, err error) {
+ if len(bytes) > 8 {
+ // We'll overflow an int64 in this case.
+ err = fmt.Errorf("integer too large")
+ return
+ }
+ for bytesRead := 0; bytesRead < len(bytes); bytesRead++ {
+ ret <<= 8
+ ret |= int64(bytes[bytesRead])
+ }
+
+ // Shift up and down in order to sign extend the result.
+ ret <<= 64 - uint8(len(bytes))*8
+ ret >>= 64 - uint8(len(bytes))*8
+ return
+}
+
+func encodeInteger(i int64) []byte {
+ n := int64Length(i)
+ out := make([]byte, n)
+
+ var j int
+ for ; n > 0; n-- {
+ out[j] = (byte(i >> uint((n-1)*8)))
+ j++
+ }
+
+ return out
+}
+
+func int64Length(i int64) (numBytes int) {
+ numBytes = 1
+
+ for i > 127 {
+ numBytes++
+ i >>= 8
+ }
+
+ for i < -128 {
+ numBytes++
+ i >>= 8
+ }
+
+ return
+}
+
+// DecodePacket decodes the given bytes into a single Packet
+// If a decode error is encountered, nil is returned.
+func DecodePacket(data []byte) *Packet {
+ p, _, _ := readPacket(bytes.NewBuffer(data))
+
+ return p
+}
+
+// DecodePacketErr decodes the given bytes into a single Packet
+// If a decode error is encountered, nil is returned
+func DecodePacketErr(data []byte) (*Packet, error) {
+ p, _, err := readPacket(bytes.NewBuffer(data))
+ if err != nil {
+ return nil, err
+ }
+ return p, nil
+}
+
+// readPacket reads a single Packet from the reader, returning the number of bytes read
+func readPacket(reader io.Reader) (*Packet, int, error) {
+ identifier, length, read, err := readHeader(reader)
+ if err != nil {
+ return nil, read, err
+ }
+
+ p := &Packet{
+ Identifier: identifier,
+ }
+
+ p.Data = new(bytes.Buffer)
+ p.Children = make([]*Packet, 0, 2)
+ p.Value = nil
+
+ if p.TagType == TypeConstructed {
+ // TODO: if universal, ensure tag type is allowed to be constructed
+
+ // Track how much content we've read
+ contentRead := 0
+ for {
+ if length != LengthIndefinite {
+ // End if we've read what we've been told to
+ if contentRead == length {
+ break
+ }
+ // Detect if a packet boundary didn't fall on the expected length
+ if contentRead > length {
+ return nil, read, fmt.Errorf("expected to read %d bytes, read %d", length, contentRead)
+ }
+ }
+
+ // Read the next packet
+ child, r, err := readPacket(reader)
+ if err != nil {
+ return nil, read, err
+ }
+ contentRead += r
+ read += r
+
+ // Test is this is the EOC marker for our packet
+ if isEOCPacket(child) {
+ if length == LengthIndefinite {
+ break
+ }
+ return nil, read, errors.New("eoc child not allowed with definite length")
+ }
+
+ // Append and continue
+ p.AppendChild(child)
+ }
+ return p, read, nil
+ }
+
+ if length == LengthIndefinite {
+ return nil, read, errors.New("indefinite length used with primitive type")
+ }
+
+ // Read definite-length content
+ content := make([]byte, length, length)
+ if length > 0 {
+ _, err := io.ReadFull(reader, content)
+ if err != nil {
+ if err == io.EOF {
+ return nil, read, io.ErrUnexpectedEOF
+ }
+ return nil, read, err
+ }
+ read += length
+ }
+
+ if p.ClassType == ClassUniversal {
+ p.Data.Write(content)
+ p.ByteValue = content
+
+ switch p.Tag {
+ case TagEOC:
+ case TagBoolean:
+ val, _ := parseInt64(content)
+
+ p.Value = val != 0
+ case TagInteger:
+ p.Value, _ = parseInt64(content)
+ case TagBitString:
+ case TagOctetString:
+ // the actual string encoding is not known here
+ // (e.g. for LDAP content is already an UTF8-encoded
+ // string). Return the data without further processing
+ p.Value = DecodeString(content)
+ case TagNULL:
+ case TagObjectIdentifier:
+ case TagObjectDescriptor:
+ case TagExternal:
+ case TagRealFloat:
+ case TagEnumerated:
+ p.Value, _ = parseInt64(content)
+ case TagEmbeddedPDV:
+ case TagUTF8String:
+ p.Value = DecodeString(content)
+ case TagRelativeOID:
+ case TagSequence:
+ case TagSet:
+ case TagNumericString:
+ case TagPrintableString:
+ p.Value = DecodeString(content)
+ case TagT61String:
+ case TagVideotexString:
+ case TagIA5String:
+ case TagUTCTime:
+ case TagGeneralizedTime:
+ case TagGraphicString:
+ case TagVisibleString:
+ case TagGeneralString:
+ case TagUniversalString:
+ case TagCharacterString:
+ case TagBMPString:
+ }
+ } else {
+ p.Data.Write(content)
+ }
+
+ return p, read, nil
+}
+
+func (p *Packet) Bytes() []byte {
+ var out bytes.Buffer
+
+ out.Write(encodeIdentifier(p.Identifier))
+ out.Write(encodeLength(p.Data.Len()))
+ out.Write(p.Data.Bytes())
+
+ return out.Bytes()
+}
+
+func (p *Packet) AppendChild(child *Packet) {
+ p.Data.Write(child.Bytes())
+ p.Children = append(p.Children, child)
+}
+
+func Encode(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
+ p := new(Packet)
+
+ p.ClassType = ClassType
+ p.TagType = TagType
+ p.Tag = Tag
+ p.Data = new(bytes.Buffer)
+
+ p.Children = make([]*Packet, 0, 2)
+
+ p.Value = Value
+ p.Description = Description
+
+ if Value != nil {
+ v := reflect.ValueOf(Value)
+
+ if ClassType == ClassUniversal {
+ switch Tag {
+ case TagOctetString:
+ sv, ok := v.Interface().(string)
+
+ if ok {
+ p.Data.Write([]byte(sv))
+ }
+ }
+ }
+ }
+
+ return p
+}
+
+func NewSequence(Description string) *Packet {
+ return Encode(ClassUniversal, TypeConstructed, TagSequence, nil, Description)
+}
+
+func NewBoolean(ClassType Class, TagType Type, Tag Tag, Value bool, Description string) *Packet {
+ intValue := int64(0)
+
+ if Value {
+ intValue = 1
+ }
+
+ p := Encode(ClassType, TagType, Tag, nil, Description)
+
+ p.Value = Value
+ p.Data.Write(encodeInteger(intValue))
+
+ return p
+}
+
+func NewInteger(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet {
+ p := Encode(ClassType, TagType, Tag, nil, Description)
+
+ p.Value = Value
+ switch v := Value.(type) {
+ case int:
+ p.Data.Write(encodeInteger(int64(v)))
+ case uint:
+ p.Data.Write(encodeInteger(int64(v)))
+ case int64:
+ p.Data.Write(encodeInteger(v))
+ case uint64:
+ // TODO : check range or add encodeUInt...
+ p.Data.Write(encodeInteger(int64(v)))
+ case int32:
+ p.Data.Write(encodeInteger(int64(v)))
+ case uint32:
+ p.Data.Write(encodeInteger(int64(v)))
+ case int16:
+ p.Data.Write(encodeInteger(int64(v)))
+ case uint16:
+ p.Data.Write(encodeInteger(int64(v)))
+ case int8:
+ p.Data.Write(encodeInteger(int64(v)))
+ case uint8:
+ p.Data.Write(encodeInteger(int64(v)))
+ default:
+ // TODO : add support for big.Int ?
+ panic(fmt.Sprintf("Invalid type %T, expected {u|}int{64|32|16|8}", v))
+ }
+
+ return p
+}
+
+func NewString(ClassType Class, TagType Type, Tag Tag, Value, Description string) *Packet {
+ p := Encode(ClassType, TagType, Tag, nil, Description)
+
+ p.Value = Value
+ p.Data.Write([]byte(Value))
+
+ return p
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber_test.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber_test.go
new file mode 100644
index 000000000..bbd22db6d
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber_test.go
@@ -0,0 +1,168 @@
+package ber
+
+import (
+ "bytes"
+ "math"
+
+ "io"
+ "testing"
+)
+
+func TestEncodeDecodeInteger(t *testing.T) {
+ for _, v := range []int64{0, 10, 128, 1024, math.MaxInt64, -1, -100, -128, -1024, math.MinInt64} {
+ enc := encodeInteger(v)
+ dec, err := parseInt64(enc)
+ if err != nil {
+ t.Fatalf("Error decoding %d : %s", v, err)
+ }
+ if v != dec {
+ t.Error("TestEncodeDecodeInteger failed for %d (got %d)", v, dec)
+ }
+
+ }
+}
+
+func TestBoolean(t *testing.T) {
+ var value bool = true
+
+ packet := NewBoolean(ClassUniversal, TypePrimitive, TagBoolean, value, "first Packet, True")
+
+ newBoolean, ok := packet.Value.(bool)
+ if !ok || newBoolean != value {
+ t.Error("error during creating packet")
+ }
+
+ encodedPacket := packet.Bytes()
+
+ newPacket := DecodePacket(encodedPacket)
+
+ newBoolean, ok = newPacket.Value.(bool)
+ if !ok || newBoolean != value {
+ t.Error("error during decoding packet")
+ }
+
+}
+
+func TestInteger(t *testing.T) {
+ var value int64 = 10
+
+ packet := NewInteger(ClassUniversal, TypePrimitive, TagInteger, value, "Integer, 10")
+
+ {
+ newInteger, ok := packet.Value.(int64)
+ if !ok || newInteger != value {
+ t.Error("error creating packet")
+ }
+ }
+
+ encodedPacket := packet.Bytes()
+
+ newPacket := DecodePacket(encodedPacket)
+
+ {
+ newInteger, ok := newPacket.Value.(int64)
+ if !ok || int64(newInteger) != value {
+ t.Error("error decoding packet")
+ }
+ }
+}
+
+func TestString(t *testing.T) {
+ var value string = "Hic sunt dracones"
+
+ packet := NewString(ClassUniversal, TypePrimitive, TagOctetString, value, "String")
+
+ newValue, ok := packet.Value.(string)
+ if !ok || newValue != value {
+ t.Error("error during creating packet")
+ }
+
+ encodedPacket := packet.Bytes()
+
+ newPacket := DecodePacket(encodedPacket)
+
+ newValue, ok = newPacket.Value.(string)
+ if !ok || newValue != value {
+ t.Error("error during decoding packet")
+ }
+
+}
+
+func TestSequenceAndAppendChild(t *testing.T) {
+
+ values := []string{
+ "HIC SVNT LEONES",
+ "Iñtërnâtiônàlizætiøn",
+ "Terra Incognita",
+ }
+
+ sequence := NewSequence("a sequence")
+ for _, s := range values {
+ sequence.AppendChild(NewString(ClassUniversal, TypePrimitive, TagOctetString, s, "String"))
+ }
+
+ if len(sequence.Children) != len(values) {
+ t.Errorf("wrong length for children array should be %d, got %d", len(values), len(sequence.Children))
+ }
+
+ encodedSequence := sequence.Bytes()
+
+ decodedSequence := DecodePacket(encodedSequence)
+ if len(decodedSequence.Children) != len(values) {
+ t.Errorf("wrong length for children array should be %d => %d", len(values), len(decodedSequence.Children))
+ }
+
+ for i, s := range values {
+ if decodedSequence.Children[i].Value.(string) != s {
+ t.Errorf("expected %d to be %q, got %q", i, s, decodedSequence.Children[i].Value.(string))
+ }
+ }
+}
+
+func TestReadPacket(t *testing.T) {
+ packet := NewString(ClassUniversal, TypePrimitive, TagOctetString, "Ad impossibilia nemo tenetur", "string")
+ var buffer io.ReadWriter
+ buffer = new(bytes.Buffer)
+
+ buffer.Write(packet.Bytes())
+
+ newPacket, err := ReadPacket(buffer)
+ if err != nil {
+ t.Error("error during ReadPacket", err)
+ }
+ newPacket.ByteValue = nil
+ if !bytes.Equal(newPacket.ByteValue, packet.ByteValue) {
+ t.Error("packets should be the same")
+ }
+}
+
+func TestBinaryInteger(t *testing.T) {
+ // data src : http://luca.ntop.org/Teaching/Appunti/asn1.html 5.7
+ var data = []struct {
+ v int64
+ e []byte
+ }{
+ {v: 0, e: []byte{0x02, 0x01, 0x00}},
+ {v: 127, e: []byte{0x02, 0x01, 0x7F}},
+ {v: 128, e: []byte{0x02, 0x02, 0x00, 0x80}},
+ {v: 256, e: []byte{0x02, 0x02, 0x01, 0x00}},
+ {v: -128, e: []byte{0x02, 0x01, 0x80}},
+ {v: -129, e: []byte{0x02, 0x02, 0xFF, 0x7F}},
+ {v: math.MaxInt64, e: []byte{0x02, 0x08, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}},
+ {v: math.MinInt64, e: []byte{0x02, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
+ }
+
+ for _, d := range data {
+ if b := NewInteger(ClassUniversal, TypePrimitive, TagInteger, int64(d.v), "").Bytes(); !bytes.Equal(d.e, b) {
+ t.Errorf("Wrong binary generated for %d : got % X, expected % X", d.v, b, d.e)
+ }
+ }
+}
+
+func TestBinaryOctetString(t *testing.T) {
+ // data src : http://luca.ntop.org/Teaching/Appunti/asn1.html 5.10
+
+ if !bytes.Equal([]byte{0x04, 0x08, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, NewString(ClassUniversal, TypePrimitive, TagOctetString, "\x01\x23\x45\x67\x89\xab\xcd\xef", "").Bytes()) {
+ t.Error("wrong binary generated")
+ }
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/content_int.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/content_int.go
new file mode 100644
index 000000000..1858b74b6
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/content_int.go
@@ -0,0 +1,25 @@
+package ber
+
+func encodeUnsignedInteger(i uint64) []byte {
+ n := uint64Length(i)
+ out := make([]byte, n)
+
+ var j int
+ for ; n > 0; n-- {
+ out[j] = (byte(i >> uint((n-1)*8)))
+ j++
+ }
+
+ return out
+}
+
+func uint64Length(i uint64) (numBytes int) {
+ numBytes = 1
+
+ for i > 255 {
+ numBytes++
+ i >>= 8
+ }
+
+ return
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header.go
new file mode 100644
index 000000000..123744e9b
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header.go
@@ -0,0 +1,29 @@
+package ber
+
+import (
+ "errors"
+ "io"
+)
+
+func readHeader(reader io.Reader) (identifier Identifier, length int, read int, err error) {
+ if i, c, err := readIdentifier(reader); err != nil {
+ return Identifier{}, 0, read, err
+ } else {
+ identifier = i
+ read += c
+ }
+
+ if l, c, err := readLength(reader); err != nil {
+ return Identifier{}, 0, read, err
+ } else {
+ length = l
+ read += c
+ }
+
+ // Validate length type with identifier (x.600, 8.1.3.2.a)
+ if length == LengthIndefinite && identifier.TagType == TypePrimitive {
+ return Identifier{}, 0, read, errors.New("indefinite length used with primitive type")
+ }
+
+ return identifier, length, read, nil
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header_test.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header_test.go
new file mode 100644
index 000000000..cac1e2e2b
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header_test.go
@@ -0,0 +1,135 @@
+package ber
+
+import (
+ "bytes"
+ "io"
+ "testing"
+)
+
+func TestReadHeader(t *testing.T) {
+ testcases := map[string]struct {
+ Data []byte
+ ExpectedIdentifier Identifier
+ ExpectedLength int
+ ExpectedBytesRead int
+ ExpectedError string
+ }{
+ "empty": {
+ Data: []byte{},
+ ExpectedIdentifier: Identifier{},
+ ExpectedLength: 0,
+ ExpectedBytesRead: 0,
+ ExpectedError: io.ErrUnexpectedEOF.Error(),
+ },
+
+ "valid short form": {
+ Data: []byte{
+ byte(ClassUniversal) | byte(TypePrimitive) | byte(TagCharacterString),
+ 127,
+ },
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypePrimitive,
+ Tag: TagCharacterString,
+ },
+ ExpectedLength: 127,
+ ExpectedBytesRead: 2,
+ ExpectedError: "",
+ },
+
+ "valid long form": {
+ Data: []byte{
+ // 2-byte encoding of tag
+ byte(ClassUniversal) | byte(TypePrimitive) | byte(HighTag),
+ byte(TagCharacterString),
+
+ // 2-byte encoding of length
+ LengthLongFormBitmask | 1,
+ 127,
+ },
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypePrimitive,
+ Tag: TagCharacterString,
+ },
+ ExpectedLength: 127,
+ ExpectedBytesRead: 4,
+ ExpectedError: "",
+ },
+
+ "valid indefinite length": {
+ Data: []byte{
+ byte(ClassUniversal) | byte(TypeConstructed) | byte(TagCharacterString),
+ LengthLongFormBitmask,
+ },
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: TagCharacterString,
+ },
+ ExpectedLength: LengthIndefinite,
+ ExpectedBytesRead: 2,
+ ExpectedError: "",
+ },
+
+ "invalid indefinite length": {
+ Data: []byte{
+ byte(ClassUniversal) | byte(TypePrimitive) | byte(TagCharacterString),
+ LengthLongFormBitmask,
+ },
+ ExpectedIdentifier: Identifier{},
+ ExpectedLength: 0,
+ ExpectedBytesRead: 2,
+ ExpectedError: "indefinite length used with primitive type",
+ },
+ }
+
+ for k, tc := range testcases {
+ reader := bytes.NewBuffer(tc.Data)
+ identifier, length, read, err := readHeader(reader)
+
+ if err != nil {
+ if tc.ExpectedError == "" {
+ t.Errorf("%s: unexpected error: %v", k, err)
+ } else if err.Error() != tc.ExpectedError {
+ t.Errorf("%s: expected error %v, got %v", k, tc.ExpectedError, err)
+ }
+ } else if tc.ExpectedError != "" {
+ t.Errorf("%s: expected error %v, got none", k, tc.ExpectedError)
+ continue
+ }
+
+ if read != tc.ExpectedBytesRead {
+ t.Errorf("%s: expected read %d, got %d", k, tc.ExpectedBytesRead, read)
+ }
+
+ if identifier.ClassType != tc.ExpectedIdentifier.ClassType {
+ t.Errorf("%s: expected class type %d (%s), got %d (%s)", k,
+ tc.ExpectedIdentifier.ClassType,
+ ClassMap[tc.ExpectedIdentifier.ClassType],
+ identifier.ClassType,
+ ClassMap[identifier.ClassType],
+ )
+ }
+ if identifier.TagType != tc.ExpectedIdentifier.TagType {
+ t.Errorf("%s: expected tag type %d (%s), got %d (%s)", k,
+ tc.ExpectedIdentifier.TagType,
+ TypeMap[tc.ExpectedIdentifier.TagType],
+ identifier.TagType,
+ TypeMap[identifier.TagType],
+ )
+ }
+ if identifier.Tag != tc.ExpectedIdentifier.Tag {
+ t.Errorf("%s: expected tag %d (%s), got %d (%s)", k,
+ tc.ExpectedIdentifier.Tag,
+ tagMap[tc.ExpectedIdentifier.Tag],
+ identifier.Tag,
+ tagMap[identifier.Tag],
+ )
+ }
+
+ if length != tc.ExpectedLength {
+ t.Errorf("%s: expected length %d, got %d", k, tc.ExpectedLength, length)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier.go
new file mode 100644
index 000000000..f7672a844
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier.go
@@ -0,0 +1,103 @@
+package ber
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "math"
+)
+
+func readIdentifier(reader io.Reader) (Identifier, int, error) {
+ identifier := Identifier{}
+ read := 0
+
+ // identifier byte
+ b, err := readByte(reader)
+ if err != nil {
+ if Debug {
+ fmt.Printf("error reading identifier byte: %v\n", err)
+ }
+ return Identifier{}, read, err
+ }
+ read++
+
+ identifier.ClassType = Class(b) & ClassBitmask
+ identifier.TagType = Type(b) & TypeBitmask
+
+ if tag := Tag(b) & TagBitmask; tag != HighTag {
+ // short-form tag
+ identifier.Tag = tag
+ return identifier, read, nil
+ }
+
+ // high-tag-number tag
+ tagBytes := 0
+ for {
+ b, err := readByte(reader)
+ if err != nil {
+ if Debug {
+ fmt.Printf("error reading high-tag-number tag byte %d: %v\n", tagBytes, err)
+ }
+ return Identifier{}, read, err
+ }
+ tagBytes++
+ read++
+
+ // Lowest 7 bits get appended to the tag value (x.690, 8.1.2.4.2.b)
+ identifier.Tag <<= 7
+ identifier.Tag |= Tag(b) & HighTagValueBitmask
+
+ // First byte may not be all zeros (x.690, 8.1.2.4.2.c)
+ if tagBytes == 1 && identifier.Tag == 0 {
+ return Identifier{}, read, errors.New("invalid first high-tag-number tag byte")
+ }
+ // Overflow of int64
+ // TODO: support big int tags?
+ if tagBytes > 9 {
+ return Identifier{}, read, errors.New("high-tag-number tag overflow")
+ }
+
+ // Top bit of 0 means this is the last byte in the high-tag-number tag (x.690, 8.1.2.4.2.a)
+ if Tag(b)&HighTagContinueBitmask == 0 {
+ break
+ }
+ }
+
+ return identifier, read, nil
+}
+
+func encodeIdentifier(identifier Identifier) []byte {
+ b := []byte{0x0}
+ b[0] |= byte(identifier.ClassType)
+ b[0] |= byte(identifier.TagType)
+
+ if identifier.Tag < HighTag {
+ // Short-form
+ b[0] |= byte(identifier.Tag)
+ } else {
+ // high-tag-number
+ b[0] |= byte(HighTag)
+
+ tag := identifier.Tag
+
+ highBit := uint(63)
+ for {
+ if tag&(1<<highBit) != 0 {
+ break
+ }
+ highBit--
+ }
+
+ tagBytes := int(math.Ceil(float64(highBit) / 7.0))
+ for i := tagBytes - 1; i >= 0; i-- {
+ offset := uint(i) * 7
+ mask := Tag(0x7f) << offset
+ tagByte := (tag & mask) >> offset
+ if i != 0 {
+ tagByte |= 0x80
+ }
+ b = append(b, byte(tagByte))
+ }
+ }
+ return b
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier_test.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier_test.go
new file mode 100644
index 000000000..7169362e2
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier_test.go
@@ -0,0 +1,344 @@
+package ber
+
+import (
+ "bytes"
+ "io"
+ "math"
+ "testing"
+)
+
+func TestReadIdentifier(t *testing.T) {
+ testcases := map[string]struct {
+ Data []byte
+
+ ExpectedIdentifier Identifier
+ ExpectedBytesRead int
+ ExpectedError string
+ }{
+ "empty": {
+ Data: []byte{},
+ ExpectedBytesRead: 0,
+ ExpectedError: io.ErrUnexpectedEOF.Error(),
+ },
+
+ "universal primitive eoc": {
+ Data: []byte{byte(ClassUniversal) | byte(TypePrimitive) | byte(TagEOC)},
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypePrimitive,
+ Tag: TagEOC,
+ },
+ ExpectedBytesRead: 1,
+ },
+ "universal primitive character string": {
+ Data: []byte{byte(ClassUniversal) | byte(TypePrimitive) | byte(TagCharacterString)},
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypePrimitive,
+ Tag: TagCharacterString,
+ },
+ ExpectedBytesRead: 1,
+ },
+
+ "universal constructed bit string": {
+ Data: []byte{byte(ClassUniversal) | byte(TypeConstructed) | byte(TagBitString)},
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: TagBitString,
+ },
+ ExpectedBytesRead: 1,
+ },
+ "universal constructed character string": {
+ Data: []byte{byte(ClassUniversal) | byte(TypeConstructed) | byte(TagCharacterString)},
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: TagCharacterString,
+ },
+ ExpectedBytesRead: 1,
+ },
+
+ "application constructed object descriptor": {
+ Data: []byte{byte(ClassApplication) | byte(TypeConstructed) | byte(TagObjectDescriptor)},
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassApplication,
+ TagType: TypeConstructed,
+ Tag: TagObjectDescriptor,
+ },
+ ExpectedBytesRead: 1,
+ },
+ "context constructed object descriptor": {
+ Data: []byte{byte(ClassContext) | byte(TypeConstructed) | byte(TagObjectDescriptor)},
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassContext,
+ TagType: TypeConstructed,
+ Tag: TagObjectDescriptor,
+ },
+ ExpectedBytesRead: 1,
+ },
+ "private constructed object descriptor": {
+ Data: []byte{byte(ClassPrivate) | byte(TypeConstructed) | byte(TagObjectDescriptor)},
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassPrivate,
+ TagType: TypeConstructed,
+ Tag: TagObjectDescriptor,
+ },
+ ExpectedBytesRead: 1,
+ },
+
+ "high-tag-number tag missing bytes": {
+ Data: []byte{byte(ClassUniversal) | byte(TypeConstructed) | byte(HighTag)},
+ ExpectedError: io.ErrUnexpectedEOF.Error(),
+ ExpectedBytesRead: 1,
+ },
+ "high-tag-number tag invalid first byte": {
+ Data: []byte{byte(ClassUniversal) | byte(TypeConstructed) | byte(HighTag), 0x0},
+ ExpectedError: "invalid first high-tag-number tag byte",
+ ExpectedBytesRead: 2,
+ },
+ "high-tag-number tag invalid first byte with continue bit": {
+ Data: []byte{byte(ClassUniversal) | byte(TypeConstructed) | byte(HighTag), byte(HighTagContinueBitmask)},
+ ExpectedError: "invalid first high-tag-number tag byte",
+ ExpectedBytesRead: 2,
+ },
+ "high-tag-number tag continuation missing bytes": {
+ Data: []byte{byte(ClassUniversal) | byte(TypeConstructed) | byte(HighTag), byte(HighTagContinueBitmask | 0x1)},
+ ExpectedError: io.ErrUnexpectedEOF.Error(),
+ ExpectedBytesRead: 2,
+ },
+ "high-tag-number tag overflow": {
+ Data: []byte{
+ byte(ClassUniversal) | byte(TypeConstructed) | byte(HighTag),
+ byte(HighTagContinueBitmask | 0x1),
+ byte(HighTagContinueBitmask | 0x1),
+ byte(HighTagContinueBitmask | 0x1),
+ byte(HighTagContinueBitmask | 0x1),
+ byte(HighTagContinueBitmask | 0x1),
+ byte(HighTagContinueBitmask | 0x1),
+ byte(HighTagContinueBitmask | 0x1),
+ byte(HighTagContinueBitmask | 0x1),
+ byte(HighTagContinueBitmask | 0x1),
+ byte(0x1),
+ },
+ ExpectedError: "high-tag-number tag overflow",
+ ExpectedBytesRead: 11,
+ },
+ "max high-tag-number tag": {
+ Data: []byte{
+ byte(ClassUniversal) | byte(TypeConstructed) | byte(HighTag),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(0x7f),
+ },
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: Tag(0x7FFFFFFFFFFFFFFF), // 01111111...(63)...11111b
+ },
+ ExpectedBytesRead: 10,
+ },
+ "high-tag-number encoding of low-tag value": {
+ Data: []byte{
+ byte(ClassUniversal) | byte(TypeConstructed) | byte(HighTag),
+ byte(TagObjectDescriptor),
+ },
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: TagObjectDescriptor,
+ },
+ ExpectedBytesRead: 2,
+ },
+ "max high-tag-number tag ignores extra data": {
+ Data: []byte{
+ byte(ClassUniversal) | byte(TypeConstructed) | byte(HighTag),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(0x7f),
+ byte(0x01), // extra data, shouldn't be read
+ byte(0x02), // extra data, shouldn't be read
+ byte(0x03), // extra data, shouldn't be read
+ },
+ ExpectedIdentifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: Tag(0x7FFFFFFFFFFFFFFF), // 01111111...(63)...11111b
+ },
+ ExpectedBytesRead: 10,
+ },
+ }
+
+ for k, tc := range testcases {
+ reader := bytes.NewBuffer(tc.Data)
+ identifier, read, err := readIdentifier(reader)
+
+ if err != nil {
+ if tc.ExpectedError == "" {
+ t.Errorf("%s: unexpected error: %v", k, err)
+ } else if err.Error() != tc.ExpectedError {
+ t.Errorf("%s: expected error %v, got %v", k, tc.ExpectedError, err)
+ }
+ } else if tc.ExpectedError != "" {
+ t.Errorf("%s: expected error %v, got none", k, tc.ExpectedError)
+ continue
+ }
+
+ if read != tc.ExpectedBytesRead {
+ t.Errorf("%s: expected read %d, got %d", k, tc.ExpectedBytesRead, read)
+ }
+
+ if identifier.ClassType != tc.ExpectedIdentifier.ClassType {
+ t.Errorf("%s: expected class type %d (%s), got %d (%s)", k,
+ tc.ExpectedIdentifier.ClassType,
+ ClassMap[tc.ExpectedIdentifier.ClassType],
+ identifier.ClassType,
+ ClassMap[identifier.ClassType],
+ )
+ }
+ if identifier.TagType != tc.ExpectedIdentifier.TagType {
+ t.Errorf("%s: expected tag type %d (%s), got %d (%s)", k,
+ tc.ExpectedIdentifier.TagType,
+ TypeMap[tc.ExpectedIdentifier.TagType],
+ identifier.TagType,
+ TypeMap[identifier.TagType],
+ )
+ }
+ if identifier.Tag != tc.ExpectedIdentifier.Tag {
+ t.Errorf("%s: expected tag %d (%s), got %d (%s)", k,
+ tc.ExpectedIdentifier.Tag,
+ tagMap[tc.ExpectedIdentifier.Tag],
+ identifier.Tag,
+ tagMap[identifier.Tag],
+ )
+ }
+ }
+}
+
+func TestEncodeIdentifier(t *testing.T) {
+ testcases := map[string]struct {
+ Identifier Identifier
+ ExpectedBytes []byte
+ }{
+ "universal primitive eoc": {
+ Identifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypePrimitive,
+ Tag: TagEOC,
+ },
+ ExpectedBytes: []byte{byte(ClassUniversal) | byte(TypePrimitive) | byte(TagEOC)},
+ },
+ "universal primitive character string": {
+ Identifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypePrimitive,
+ Tag: TagCharacterString,
+ },
+ ExpectedBytes: []byte{byte(ClassUniversal) | byte(TypePrimitive) | byte(TagCharacterString)},
+ },
+
+ "universal constructed bit string": {
+ Identifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: TagBitString,
+ },
+ ExpectedBytes: []byte{byte(ClassUniversal) | byte(TypeConstructed) | byte(TagBitString)},
+ },
+ "universal constructed character string": {
+ Identifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: TagCharacterString,
+ },
+ ExpectedBytes: []byte{byte(ClassUniversal) | byte(TypeConstructed) | byte(TagCharacterString)},
+ },
+
+ "application constructed object descriptor": {
+ Identifier: Identifier{
+ ClassType: ClassApplication,
+ TagType: TypeConstructed,
+ Tag: TagObjectDescriptor,
+ },
+ ExpectedBytes: []byte{byte(ClassApplication) | byte(TypeConstructed) | byte(TagObjectDescriptor)},
+ },
+ "context constructed object descriptor": {
+ Identifier: Identifier{
+ ClassType: ClassContext,
+ TagType: TypeConstructed,
+ Tag: TagObjectDescriptor,
+ },
+ ExpectedBytes: []byte{byte(ClassContext) | byte(TypeConstructed) | byte(TagObjectDescriptor)},
+ },
+ "private constructed object descriptor": {
+ Identifier: Identifier{
+ ClassType: ClassPrivate,
+ TagType: TypeConstructed,
+ Tag: TagObjectDescriptor,
+ },
+ ExpectedBytes: []byte{byte(ClassPrivate) | byte(TypeConstructed) | byte(TagObjectDescriptor)},
+ },
+
+ "max low-tag-number tag": {
+ Identifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: TagBMPString,
+ },
+ ExpectedBytes: []byte{
+ byte(ClassUniversal) | byte(TypeConstructed) | byte(TagBMPString),
+ },
+ },
+
+ "min high-tag-number tag": {
+ Identifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: TagBMPString + 1,
+ },
+ ExpectedBytes: []byte{
+ byte(ClassUniversal) | byte(TypeConstructed) | byte(HighTag),
+ byte(TagBMPString + 1),
+ },
+ },
+
+ "max high-tag-number tag": {
+ Identifier: Identifier{
+ ClassType: ClassUniversal,
+ TagType: TypeConstructed,
+ Tag: Tag(math.MaxInt64),
+ },
+ ExpectedBytes: []byte{
+ byte(ClassUniversal) | byte(TypeConstructed) | byte(HighTag),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(HighTagContinueBitmask | 0x7f),
+ byte(0x7f),
+ },
+ },
+ }
+
+ for k, tc := range testcases {
+ b := encodeIdentifier(tc.Identifier)
+ if bytes.Compare(tc.ExpectedBytes, b) != 0 {
+ t.Errorf("%s: Expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedBytes, b)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length.go
new file mode 100644
index 000000000..8e2ae4ddd
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length.go
@@ -0,0 +1,71 @@
+package ber
+
+import (
+ "errors"
+ "fmt"
+ "io"
+)
+
+func readLength(reader io.Reader) (length int, read int, err error) {
+ // length byte
+ b, err := readByte(reader)
+ if err != nil {
+ if Debug {
+ fmt.Printf("error reading length byte: %v\n", err)
+ }
+ return 0, 0, err
+ }
+ read++
+
+ switch {
+ case b == 0xFF:
+ // Invalid 0xFF (x.600, 8.1.3.5.c)
+ return 0, read, errors.New("invalid length byte 0xff")
+
+ case b == LengthLongFormBitmask:
+ // Indefinite form, we have to decode packets until we encounter an EOC packet (x.600, 8.1.3.6)
+ length = LengthIndefinite
+
+ case b&LengthLongFormBitmask == 0:
+ // Short definite form, extract the length from the bottom 7 bits (x.600, 8.1.3.4)
+ length = int(b) & LengthValueBitmask
+
+ case b&LengthLongFormBitmask != 0:
+ // Long definite form, extract the number of length bytes to follow from the bottom 7 bits (x.600, 8.1.3.5.b)
+ lengthBytes := int(b) & LengthValueBitmask
+ // Protect against overflow
+ // TODO: support big int length?
+ if lengthBytes > 8 {
+ return 0, read, errors.New("long-form length overflow")
+ }
+ for i := 0; i < lengthBytes; i++ {
+ b, err = readByte(reader)
+ if err != nil {
+ if Debug {
+ fmt.Printf("error reading long-form length byte %d: %v\n", i, err)
+ }
+ return 0, read, err
+ }
+ read++
+
+ // x.600, 8.1.3.5
+ length <<= 8
+ length |= int(b)
+ }
+
+ default:
+ return 0, read, errors.New("invalid length byte")
+ }
+
+ return length, read, nil
+}
+
+func encodeLength(length int) []byte {
+ length_bytes := encodeUnsignedInteger(uint64(length))
+ if length > 127 || len(length_bytes) > 1 {
+ longFormBytes := []byte{(LengthLongFormBitmask | byte(len(length_bytes)))}
+ longFormBytes = append(longFormBytes, length_bytes...)
+ length_bytes = longFormBytes
+ }
+ return length_bytes
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length_test.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length_test.go
new file mode 100644
index 000000000..afe0e8037
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length_test.go
@@ -0,0 +1,158 @@
+package ber
+
+import (
+ "bytes"
+ "io"
+ "math"
+ "testing"
+)
+
+func TestReadLength(t *testing.T) {
+ testcases := map[string]struct {
+ Data []byte
+
+ ExpectedLength int
+ ExpectedBytesRead int
+ ExpectedError string
+ }{
+ "empty": {
+ Data: []byte{},
+ ExpectedBytesRead: 0,
+ ExpectedError: io.ErrUnexpectedEOF.Error(),
+ },
+ "invalid first byte": {
+ Data: []byte{0xFF},
+ ExpectedBytesRead: 1,
+ ExpectedError: "invalid length byte 0xff",
+ },
+
+ "indefinite form": {
+ Data: []byte{LengthLongFormBitmask},
+ ExpectedLength: LengthIndefinite,
+ ExpectedBytesRead: 1,
+ },
+
+ "short-definite-form zero length": {
+ Data: []byte{0},
+ ExpectedLength: 0,
+ ExpectedBytesRead: 1,
+ },
+ "short-definite-form length 1": {
+ Data: []byte{1},
+ ExpectedLength: 1,
+ ExpectedBytesRead: 1,
+ },
+ "short-definite-form max length": {
+ Data: []byte{127},
+ ExpectedLength: 127,
+ ExpectedBytesRead: 1,
+ },
+
+ "long-definite-form missing bytes": {
+ Data: []byte{LengthLongFormBitmask | 1},
+ ExpectedBytesRead: 1,
+ ExpectedError: io.ErrUnexpectedEOF.Error(),
+ },
+ "long-definite-form overflow": {
+ Data: []byte{LengthLongFormBitmask | 9},
+ ExpectedBytesRead: 1,
+ ExpectedError: "long-form length overflow",
+ },
+ "long-definite-form zero length": {
+ Data: []byte{LengthLongFormBitmask | 1, 0x0},
+ ExpectedLength: 0,
+ ExpectedBytesRead: 2,
+ },
+ "long-definite-form length 127": {
+ Data: []byte{LengthLongFormBitmask | 1, 127},
+ ExpectedLength: 127,
+ ExpectedBytesRead: 2,
+ },
+ "long-definite-form max length": {
+ Data: []byte{
+ LengthLongFormBitmask | 8,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ },
+ ExpectedLength: math.MaxInt64,
+ ExpectedBytesRead: 9,
+ },
+ }
+
+ for k, tc := range testcases {
+ reader := bytes.NewBuffer(tc.Data)
+ length, read, err := readLength(reader)
+
+ if err != nil {
+ if tc.ExpectedError == "" {
+ t.Errorf("%s: unexpected error: %v", k, err)
+ } else if err.Error() != tc.ExpectedError {
+ t.Errorf("%s: expected error %v, got %v", k, tc.ExpectedError, err)
+ }
+ } else if tc.ExpectedError != "" {
+ t.Errorf("%s: expected error %v, got none", k, tc.ExpectedError)
+ continue
+ }
+
+ if read != tc.ExpectedBytesRead {
+ t.Errorf("%s: expected read %d, got %d", k, tc.ExpectedBytesRead, read)
+ }
+
+ if length != tc.ExpectedLength {
+ t.Errorf("%s: expected length %d, got %d", k, tc.ExpectedLength, length)
+ }
+ }
+}
+
+func TestEncodeLength(t *testing.T) {
+ testcases := map[string]struct {
+ Length int
+ ExpectedBytes []byte
+ }{
+ "0": {
+ Length: 0,
+ ExpectedBytes: []byte{0},
+ },
+ "1": {
+ Length: 1,
+ ExpectedBytes: []byte{1},
+ },
+
+ "max short-form length": {
+ Length: 127,
+ ExpectedBytes: []byte{127},
+ },
+ "min long-form length": {
+ Length: 128,
+ ExpectedBytes: []byte{LengthLongFormBitmask | 1, 128},
+ },
+
+ "max long-form length": {
+ Length: math.MaxInt64,
+ ExpectedBytes: []byte{
+ LengthLongFormBitmask | 8,
+ 0x7F,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ },
+ },
+ }
+
+ for k, tc := range testcases {
+ b := encodeLength(tc.Length)
+ if bytes.Compare(tc.ExpectedBytes, b) != 0 {
+ t.Errorf("%s: Expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedBytes, b)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/suite_test.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/suite_test.go
new file mode 100644
index 000000000..ace8e6705
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/suite_test.go
@@ -0,0 +1,182 @@
+package ber
+
+import (
+ "bytes"
+ "io"
+ "io/ioutil"
+ "testing"
+)
+
+var errEOF = io.ErrUnexpectedEOF.Error()
+
+// Tests from http://www.strozhevsky.com/free_docs/free_asn1_testsuite_descr.pdf
+// Source files and descriptions at http://www.strozhevsky.com/free_docs/TEST_SUITE.zip
+var testcases = []struct {
+ // File contains the path to the BER-encoded file
+ File string
+ // Error indicates whether a decoding error is expected
+ Error string
+ // AbnormalEncoding indicates whether a normalized re-encoding is expected to differ from the original source
+ AbnormalEncoding bool
+ // IndefiniteEncoding indicates the source file used indefinite-length encoding, so the re-encoding is expected to differ (since the length is known)
+ IndefiniteEncoding bool
+}{
+ // Common blocks
+ {File: "tests/tc1.ber", Error: "high-tag-number tag overflow"},
+ {File: "tests/tc2.ber", Error: errEOF},
+ {File: "tests/tc3.ber", Error: errEOF},
+ {File: "tests/tc4.ber", Error: "invalid length byte 0xff"},
+ {File: "tests/tc5.ber", Error: "", AbnormalEncoding: true},
+ // Real numbers (some expected failures are disabled until support is added)
+ {File: "tests/tc6.ber", Error: ""}, // Error: "REAL value +0 must be encoded with zero-length value block"},
+ {File: "tests/tc7.ber", Error: ""}, // Error: "REAL value -0 must be encoded as a special value"},
+ {File: "tests/tc8.ber", Error: ""},
+ {File: "tests/tc9.ber", Error: ""}, // Error: "Bits 6 and 5 of information octet for REAL are equal to 11"
+ {File: "tests/tc10.ber", Error: ""},
+ {File: "tests/tc11.ber", Error: ""}, // Error: "Incorrect NR form"
+ {File: "tests/tc12.ber", Error: ""}, // Error: "Encoding of "special value" not from ASN.1 standard"
+ {File: "tests/tc13.ber", Error: errEOF},
+ {File: "tests/tc14.ber", Error: errEOF},
+ {File: "tests/tc15.ber", Error: ""}, // Error: "Too big value of exponent"
+ {File: "tests/tc16.ber", Error: ""}, // Error: "Too big value of mantissa"
+ {File: "tests/tc17.ber", Error: ""}, // Error: "Too big values for exponent and mantissa + using of "scaling factor" value"
+ // Integers
+ {File: "tests/tc18.ber", Error: ""},
+ {File: "tests/tc19.ber", Error: errEOF},
+ {File: "tests/tc20.ber", Error: ""},
+ // Object identifiers
+ {File: "tests/tc21.ber", Error: ""},
+ {File: "tests/tc22.ber", Error: ""},
+ {File: "tests/tc23.ber", Error: errEOF},
+ {File: "tests/tc24.ber", Error: ""},
+ // Booleans
+ {File: "tests/tc25.ber", Error: ""},
+ {File: "tests/tc26.ber", Error: ""},
+ {File: "tests/tc27.ber", Error: errEOF},
+ {File: "tests/tc28.ber", Error: ""},
+ {File: "tests/tc29.ber", Error: ""},
+ // Null
+ {File: "tests/tc30.ber", Error: ""},
+ {File: "tests/tc31.ber", Error: errEOF},
+ {File: "tests/tc32.ber", Error: ""},
+ // Bitstring (some expected failures are disabled until support is added)
+ {File: "tests/tc33.ber", Error: ""}, // Error: "Too big value for "unused bits""
+ {File: "tests/tc34.ber", Error: errEOF},
+ {File: "tests/tc35.ber", Error: "", IndefiniteEncoding: true}, // Error: "Using of different from BIT STRING types as internal types for constructive encoding"
+ {File: "tests/tc36.ber", Error: "", IndefiniteEncoding: true}, // Error: "Using of "unused bits" in internal BIT STRINGs with constructive form of encoding"
+ {File: "tests/tc37.ber", Error: ""},
+ {File: "tests/tc38.ber", Error: "", IndefiniteEncoding: true},
+ {File: "tests/tc39.ber", Error: ""},
+ {File: "tests/tc40.ber", Error: ""},
+ // Octet string (some expected failures are disabled until support is added)
+ {File: "tests/tc41.ber", Error: "", IndefiniteEncoding: true}, // Error: "Using of different from OCTET STRING types as internal types for constructive encoding"
+ {File: "tests/tc42.ber", Error: errEOF},
+ {File: "tests/tc43.ber", Error: errEOF},
+ {File: "tests/tc44.ber", Error: ""},
+ {File: "tests/tc45.ber", Error: ""},
+ // Bitstring
+ {File: "tests/tc46.ber", Error: "indefinite length used with primitive type"},
+ {File: "tests/tc47.ber", Error: "eoc child not allowed with definite length"},
+ {File: "tests/tc48.ber", Error: "", IndefiniteEncoding: true}, // Error: "Using of more than 7 "unused bits" in BIT STRING with constrictive encoding form"
+}
+
+func TestSuiteDecodePacket(t *testing.T) {
+ // Debug = true
+ for _, tc := range testcases {
+ file := tc.File
+
+ dataIn, err := ioutil.ReadFile(file)
+ if err != nil {
+ t.Errorf("%s: %v", file, err)
+ continue
+ }
+
+ // fmt.Printf("%s: decode %d\n", file, len(dataIn))
+ packet, err := DecodePacketErr(dataIn)
+ if err != nil {
+ if tc.Error == "" {
+ t.Errorf("%s: unexpected error during DecodePacket: %v", file, err)
+ } else if tc.Error != err.Error() {
+ t.Errorf("%s: expected error %q during DecodePacket, got %q", file, tc.Error, err)
+ }
+ continue
+ }
+ if tc.Error != "" {
+ t.Errorf("%s: expected error %q, got none", file, tc.Error)
+ continue
+ }
+
+ dataOut := packet.Bytes()
+ if tc.AbnormalEncoding || tc.IndefiniteEncoding {
+ // Abnormal encodings and encodings that used indefinite length should re-encode differently
+ if bytes.Equal(dataOut, dataIn) {
+ t.Errorf("%s: data should have been re-encoded differently", file)
+ }
+ } else if !bytes.Equal(dataOut, dataIn) {
+ // Make sure the serialized data matches the source
+ t.Errorf("%s: data should be the same", file)
+ }
+
+ packet, err = DecodePacketErr(dataOut)
+ if err != nil {
+ t.Errorf("%s: unexpected error: %v", file, err)
+ continue
+ }
+
+ // Make sure the re-serialized data matches our original serialization
+ dataOut2 := packet.Bytes()
+ if !bytes.Equal(dataOut, dataOut2) {
+ t.Errorf("%s: data should be the same", file)
+ }
+ }
+}
+
+func TestSuiteReadPacket(t *testing.T) {
+ for _, tc := range testcases {
+ file := tc.File
+
+ dataIn, err := ioutil.ReadFile(file)
+ if err != nil {
+ t.Errorf("%s: %v", file, err)
+ continue
+ }
+
+ buffer := bytes.NewBuffer(dataIn)
+ packet, err := ReadPacket(buffer)
+ if err != nil {
+ if tc.Error == "" {
+ t.Errorf("%s: unexpected error during ReadPacket: %v", file, err)
+ } else if tc.Error != err.Error() {
+ t.Errorf("%s: expected error %q during ReadPacket, got %q", file, tc.Error, err)
+ }
+ continue
+ }
+ if tc.Error != "" {
+ t.Errorf("%s: expected error %q, got none", file, tc.Error)
+ continue
+ }
+
+ dataOut := packet.Bytes()
+ if tc.AbnormalEncoding || tc.IndefiniteEncoding {
+ // Abnormal encodings and encodings that used indefinite length should re-encode differently
+ if bytes.Equal(dataOut, dataIn) {
+ t.Errorf("%s: data should have been re-encoded differently", file)
+ }
+ } else if !bytes.Equal(dataOut, dataIn) {
+ // Make sure the serialized data matches the source
+ t.Errorf("%s: data should be the same", file)
+ }
+
+ packet, err = DecodePacketErr(dataOut)
+ if err != nil {
+ t.Errorf("%s: unexpected error: %v", file, err)
+ continue
+ }
+
+ // Make sure the re-serialized data matches our original serialization
+ dataOut2 := packet.Bytes()
+ if !bytes.Equal(dataOut, dataOut2) {
+ t.Errorf("%s: data should be the same", file)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc1.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc1.ber
new file mode 100644
index 000000000..5c6ba1c6a
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc1.ber
@@ -0,0 +1 @@
+Ÿÿÿÿÿÿÿÿÿÿ@ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc10.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc10.ber
new file mode 100644
index 000000000..f733125d4
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc10.ber
@@ -0,0 +1 @@
+ ƒÿÿÿû \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc11.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc11.ber
new file mode 100644
index 000000000..cc4a609c8
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc11.ber
@@ -0,0 +1 @@
+  015625 \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc12.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc12.ber
new file mode 100644
index 000000000..dbb538d69
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc12.ber
@@ -0,0 +1 @@
+ I \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc13.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc13.ber
new file mode 100644
index 000000000..f4f438e0d
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc13.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc14.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc14.ber
new file mode 100644
index 000000000..b6f2fd3a4
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc14.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc15.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc15.ber
new file mode 100644
index 000000000..3d6da6764
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc15.ber
@@ -0,0 +1 @@
+ ƒ ÿÿÿÿÿÿÿû \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc16.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc16.ber
new file mode 100644
index 000000000..68634f5f3
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc16.ber
@@ -0,0 +1 @@
+ €û \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc17.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc17.ber
new file mode 100644
index 000000000..adb9e3320
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc17.ber
@@ -0,0 +1 @@
+ ¯ þÿÿÿÿÿÿÿÿ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc18.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc18.ber
new file mode 100644
index 000000000..fb6843f7f
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc18.ber
@@ -0,0 +1 @@
+ÿð \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc19.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc19.ber
new file mode 100644
index 000000000..03afaa5de
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc19.ber
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc2.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc2.ber
new file mode 100644
index 000000000..7e785773c
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc2.ber
@@ -0,0 +1 @@
+Ÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc20.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc20.ber
new file mode 100644
index 000000000..a976464b9
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc20.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc21.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc21.ber
new file mode 100644
index 000000000..d6c2f9aa7
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc21.ber
@@ -0,0 +1 @@
+€€Q€€ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc22.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc22.ber
new file mode 100644
index 000000000..d1d70afab
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc22.ber
@@ -0,0 +1 @@
+ÿÿÿÿÿÿÿÿÿÿ… \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc23.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc23.ber
new file mode 100644
index 000000000..0e8d18f62
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc23.ber
@@ -0,0 +1 @@
+ÿÿÿÿÿ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc24.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc24.ber
new file mode 100644
index 000000000..10565aefa
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc24.ber
@@ -0,0 +1 @@
+Î`†HˆŸO …îåJ…ä¿c‹Û/ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc25.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc25.ber
new file mode 100644
index 000000000..1e1140524
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc25.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc26.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc26.ber
new file mode 100644
index 000000000..d28653b3b
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc26.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc27.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc27.ber
new file mode 100644
index 000000000..c8c781144
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc27.ber
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc28.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc28.ber
new file mode 100644
index 000000000..415fe23ed
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc28.ber
@@ -0,0 +1 @@
+ÿ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc29.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc29.ber
new file mode 100644
index 000000000..4076f4487
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc29.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc3.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc3.ber
new file mode 100644
index 000000000..c05c900b6
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc3.ber
@@ -0,0 +1 @@
+Ÿÿÿÿÿÿÿÿÿ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc30.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc30.ber
new file mode 100644
index 000000000..72bcf80f4
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc30.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc31.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc31.ber
new file mode 100644
index 000000000..1fcc4f254
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc31.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc32.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc32.ber
new file mode 100644
index 000000000..19b3e940a
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc32.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc33.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc33.ber
new file mode 100644
index 000000000..6ea70c4d2
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc33.ber
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc34.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc34.ber
new file mode 100644
index 000000000..61337095d
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc34.ber
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc35.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc35.ber
new file mode 100644
index 000000000..d27eb301a
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc35.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc36.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc36.ber
new file mode 100644
index 000000000..e5baaeacd
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc36.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc37.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc37.ber
new file mode 100644
index 000000000..d0b1cfbe1
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc37.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc38.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc38.ber
new file mode 100644
index 000000000..090bce74b
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc38.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc39.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc39.ber
new file mode 100644
index 000000000..d9d01199b
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc39.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc4.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc4.ber
new file mode 100644
index 000000000..2b888baac
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc4.ber
@@ -0,0 +1 @@
+Ÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc40.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc40.ber
new file mode 100644
index 000000000..15294a501
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc40.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc41.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc41.ber
new file mode 100644
index 000000000..276836b65
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc41.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc42.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc42.ber
new file mode 100644
index 000000000..21cbfd10f
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc42.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc43.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc43.ber
new file mode 100644
index 000000000..98dbd7419
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc43.ber
@@ -0,0 +1 @@
+$ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc44.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc44.ber
new file mode 100644
index 000000000..d825e1ad7
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc44.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc45.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc45.ber
new file mode 100644
index 000000000..7b861b02c
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc45.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc46.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc46.ber
new file mode 100644
index 000000000..e78deee34
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc46.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc47.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc47.ber
new file mode 100644
index 000000000..190bb86f6
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc47.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc48.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc48.ber
new file mode 100644
index 000000000..f7f111ae6
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc48.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc5.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc5.ber
new file mode 100644
index 000000000..45e0a0093
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc5.ber
@@ -0,0 +1 @@
+Ÿÿÿÿÿÿÿÿÿ@ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc6.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc6.ber
new file mode 100644
index 000000000..cee1aaf0c
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc6.ber
@@ -0,0 +1 @@
+ +0.E-5 \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc7.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc7.ber
new file mode 100644
index 000000000..d5ae68572
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc7.ber
@@ -0,0 +1 @@
+ -0.E-5 \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc8.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc8.ber
new file mode 100644
index 000000000..cb32a09cb
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc8.ber
Binary files differ
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc9.ber b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc9.ber
new file mode 100644
index 000000000..50b43a510
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc9.ber
@@ -0,0 +1 @@
+ ¼þ \ No newline at end of file
diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/util.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/util.go
new file mode 100644
index 000000000..3e56b66c8
--- /dev/null
+++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/util.go
@@ -0,0 +1,24 @@
+package ber
+
+import "io"
+
+func readByte(reader io.Reader) (byte, error) {
+ bytes := make([]byte, 1, 1)
+ _, err := io.ReadFull(reader, bytes)
+ if err != nil {
+ if err == io.EOF {
+ return 0, io.ErrUnexpectedEOF
+ }
+ return 0, err
+ }
+ return bytes[0], nil
+}
+
+func isEOCPacket(p *Packet) bool {
+ return p != nil &&
+ p.Tag == TagEOC &&
+ p.ClassType == ClassUniversal &&
+ p.TagType == TypePrimitive &&
+ len(p.ByteValue) == 0 &&
+ len(p.Children) == 0
+}
diff --git a/Makefile b/Makefile
index 3d08909ad..9fd74b959 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,9 @@ BUILD_NUMBER ?= $(BUILD_NUMBER:)
BUILD_DATE = $(shell date -u)
BUILD_HASH = $(shell git rev-parse HEAD)
+ENTERPRISE_DIR ?= ../enterprise
+BUILD_ENTERPRISE ?= true
+
GO=$(GOPATH)/bin/godep go
ESLINT=node_modules/eslint/bin/eslint.js
@@ -31,6 +34,10 @@ all: dist-local
dist: | build-server build-client go-test package
mv ./model/version.go.bak ./model/version.go
+ @if [ "$(BUILD_ENTERPRISE)" = "true" ] && [ -d "$(ENTERPRISE_DIR)" ]; then \
+ mv ./mattermost.go.bak ./mattermost.go; \
+ mv ./config/config.json.bak ./config/config.json 2> /dev/null || true; \
+ fi
dist-local: | start-docker dist
@@ -79,9 +86,21 @@ build-server:
sed -i'.make_mac_work' 's|_BUILD_NUMBER_|$(BUILD_NUMBER)|g' ./model/version.go
sed -i'.make_mac_work' 's|_BUILD_DATE_|$(BUILD_DATE)|g' ./model/version.go
sed -i'.make_mac_work' 's|_BUILD_HASH_|$(BUILD_HASH)|g' ./model/version.go
+
+ @if [ "$(BUILD_ENTERPRISE)" = "true" ] && [ -d "$(ENTERPRISE_DIR)" ]; then \
+ cp ./config/config.json ./config/config.json.bak; \
+ jq -s '.[0] * .[1]' ./config/config.json $(ENTERPRISE_DIR)/config/enterprise-config-additions.json > config.json.tmp; \
+ mv config.json.tmp ./config/config.json; \
+ sed -e '/\/\/ENTERPRISE_IMPORTS/ {' -e 'r $(ENTERPRISE_DIR)/imports' -e 'd' -e '}' -i'.bak' mattermost.go; \
+ sed -i'.make_mac_work' 's|_BUILD_ENTERPRISE_READY_|true|g' ./model/version.go; \
+ else \
+ sed -i'.make_mac_work' 's|_BUILD_ENTERPRISE_READY_|false|g' ./model/version.go; \
+ fi
+
rm ./model/version.go.make_mac_work
$(GO) build $(GOFLAGS) ./...
+ $(GO) generate $(GOFLAGS) ./...
$(GO) install $(GOFLAGS) ./...
package:
@@ -242,6 +261,13 @@ run: start-docker .prepare-go .prepare-jsx
@echo Starting react processo
cd web/react && npm start &
+ @if [ "$(BUILD_ENTERPRISE)" = "true" ] && [ -d "$(ENTERPRISE_DIR)" ]; then \
+ cp ./config/config.json ./config/config.json.bak; \
+ jq -s '.[0] * .[1]' ./config/config.json $(ENTERPRISE_DIR)/config/enterprise-config-additions.json > config.json.tmp; \
+ mv config.json.tmp ./config/config.json; \
+ sed -e '/\/\/ENTERPRISE_IMPORTS/ {' -e 'r $(ENTERPRISE_DIR)/imports' -e 'd' -e '}' -i'.bak' mattermost.go; \
+ fi
+
@echo Starting go web server
$(GO) run $(GOFLAGS) mattermost.go -config=config.json &
@@ -270,6 +296,11 @@ stop:
docker rm -v ${DOCKER_CONTAINER_NAME} > /dev/null; \
fi
+ @if [ "$(BUILD_ENTERPRISE)" = "true" ] && [ -d "$(ENTERPRISE_DIR)" ]; then \
+ mv ./config/config.json.bak ./config/config.json 2> /dev/null || true; \
+ mv ./mattermost.go.bak ./mattermost.go 2> /dev/null || true; \
+ fi
+
setup-mac:
echo $$(boot2docker ip 2> /dev/null) dockerhost | sudo tee -a /etc/hosts
diff --git a/api/team.go b/api/team.go
index 0918b40e2..dd9bd0bac 100644
--- a/api/team.go
+++ b/api/team.go
@@ -221,8 +221,9 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
teamSignup.User.TeamId = rteam.Id
teamSignup.User.EmailVerified = true
- ruser := CreateUser(c, rteam, &teamSignup.User)
- if c.Err != nil {
+ ruser, err := CreateUser(rteam, &teamSignup.User)
+ if err != nil {
+ c.Err = err
return
}
diff --git a/api/user.go b/api/user.go
index 886e38c91..1df8fff73 100644
--- a/api/user.go
+++ b/api/user.go
@@ -11,6 +11,7 @@ import (
"github.com/disintegration/imaging"
"github.com/golang/freetype"
"github.com/gorilla/mux"
+ "github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
@@ -44,6 +45,7 @@ func InitUser(r *mux.Router) {
sr.Handle("/reset_password", ApiAppHandler(resetPassword)).Methods("POST")
sr.Handle("/login", ApiAppHandler(login)).Methods("POST")
sr.Handle("/logout", ApiUserRequired(logout)).Methods("POST")
+ sr.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST")
sr.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST")
sr.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST")
@@ -59,7 +61,7 @@ func InitUser(r *mux.Router) {
}
func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
- if !utils.Cfg.EmailSettings.EnableSignUpWithEmail {
+ if !utils.Cfg.EmailSettings.EnableSignUpWithEmail || !utils.Cfg.TeamSettings.EnableUserCreation {
c.Err = model.NewAppError("signupTeam", "User sign-up with email is disabled.", "")
c.Err.StatusCode = http.StatusNotImplemented
return
@@ -118,8 +120,9 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
user.EmailVerified = true
}
- ruser := CreateUser(c, team, user)
- if c.Err != nil {
+ ruser, err := CreateUser(team, user)
+ if err != nil {
+ c.Err = err
return
}
@@ -163,12 +166,7 @@ func IsVerifyHashRequired(user *model.User, team *model.Team, hash string) bool
return shouldVerifyHash
}
-func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
-
- if !utils.Cfg.TeamSettings.EnableUserCreation {
- c.Err = model.NewAppError("CreateUser", "User creation has been disabled. Please ask your systems administrator for details.", "")
- return nil
- }
+func CreateUser(team *model.Team, user *model.User) (*model.User, *model.AppError) {
channelRole := ""
if team.Email == user.Email {
@@ -178,8 +176,7 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
// Below is a speical case where the first user in the entire
// system is granted the system_admin role instead of admin
if result := <-Srv.Store.User().GetTotalUsersCount(); result.Err != nil {
- c.Err = result.Err
- return nil
+ return nil, result.Err
} else {
count := result.Data.(int64)
if count <= 0 {
@@ -194,9 +191,8 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
user.MakeNonNil()
if result := <-Srv.Store.User().Save(user); result.Err != nil {
- c.Err = result.Err
l4g.Error("Couldn't save the user err=%v", result.Err)
- return nil
+ return nil, result.Err
} else {
ruser := result.Data.(*model.User)
@@ -225,7 +221,7 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
PublishAndForget(message)
- return ruser
+ return ruser, nil
}
}
@@ -313,7 +309,7 @@ func LoginById(c *Context, w http.ResponseWriter, r *http.Request, userId, passw
return nil
} else {
user := result.Data.(*model.User)
- if checkUserPassword(c, user, password) {
+ if checkUserLoginAttempts(c, user) && checkUserPassword(c, user, password) {
Login(c, w, r, user, deviceId)
return user
}
@@ -339,7 +335,7 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam
} else {
user := result.Data.(*model.User)
- if checkUserPassword(c, user, password) {
+ if checkUserLoginAttempts(c, user) && checkUserPassword(c, user, password) {
Login(c, w, r, user, deviceId)
return user
}
@@ -348,8 +344,7 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam
return nil
}
-func checkUserPassword(c *Context, user *model.User, password string) bool {
-
+func checkUserLoginAttempts(c *Context, user *model.User) bool {
if user.FailedAttempts >= utils.Cfg.ServiceSettings.MaximumLoginAttempts {
c.LogAuditWithUserId(user.Id, "fail")
c.Err = model.NewAppError("checkUserPassword", "Your account is locked because of too many failed password attempts. Please reset your password.", "user_id="+user.Id)
@@ -357,6 +352,11 @@ func checkUserPassword(c *Context, user *model.User, password string) bool {
return false
}
+ return true
+}
+
+func checkUserPassword(c *Context, user *model.User, password string) bool {
+
if !model.ComparePassword(user.Password, password) {
c.LogAuditWithUserId(user.Id, "fail")
c.Err = model.NewAppError("checkUserPassword", "Login failed because of invalid password", "user_id="+user.Id)
@@ -500,6 +500,70 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(user.ToJson()))
}
+func loginLdap(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !*utils.Cfg.LdapSettings.Enable {
+ c.Err = model.NewAppError("loginLdap", "LDAP not enabled on this server", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
+
+ props := model.MapFromJson(r.Body)
+
+ password := props["password"]
+ id := props["id"]
+ teamName := props["teamName"]
+
+ if len(password) == 0 {
+ c.Err = model.NewAppError("loginLdap", "Password field must not be blank", "")
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
+
+ if len(id) == 0 {
+ c.Err = model.NewAppError("loginLdap", "Need an ID", "")
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
+
+ teamc := Srv.Store.Team().GetByName(teamName)
+
+ ldapInterface := einterfaces.GetLdapInterface()
+ if ldapInterface == nil {
+ c.Err = model.NewAppError("loginLdap", "LDAP not available on this server", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
+
+ var team *model.Team
+ if result := <-teamc; result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ team = result.Data.(*model.Team)
+ }
+
+ user, err := ldapInterface.DoLogin(team, id, password)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ if !checkUserLoginAttempts(c, user) {
+ return
+ }
+
+ // User is authenticated at this point
+
+ Login(c, w, r, user, props["device_id"])
+
+ if user != nil {
+ user.Sanitize(map[string]bool{})
+ } else {
+ user = &model.User{}
+ }
+ w.Write([]byte(user.ToJson()))
+}
+
func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
id := props["id"]
diff --git a/einterfaces/ldap.go b/einterfaces/ldap.go
new file mode 100644
index 000000000..d4bfadc21
--- /dev/null
+++ b/einterfaces/ldap.go
@@ -0,0 +1,22 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package einterfaces
+
+import (
+ "github.com/mattermost/platform/model"
+)
+
+type LdapInterface interface {
+ DoLogin(team *model.Team, id string, password string) (*model.User, *model.AppError)
+}
+
+var theLdapInterface LdapInterface
+
+func RegisterLdapInterface(newInterface LdapInterface) {
+ theLdapInterface = newInterface
+}
+
+func GetLdapInterface() LdapInterface {
+ return theLdapInterface
+}
diff --git a/einterfaces/oauthproviders.go b/einterfaces/oauthproviders.go
new file mode 100644
index 000000000..c2ea8c72d
--- /dev/null
+++ b/einterfaces/oauthproviders.go
@@ -0,0 +1,29 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package einterfaces
+
+import (
+ "github.com/mattermost/platform/model"
+ "io"
+)
+
+type OauthProvider interface {
+ GetIdentifier() string
+ GetUserFromJson(data io.Reader) *model.User
+ GetAuthDataFromJson(data io.Reader) string
+}
+
+var oauthProviders = make(map[string]OauthProvider)
+
+func RegisterOauthProvider(name string, newProvider OauthProvider) {
+ oauthProviders[name] = newProvider
+}
+
+func GetOauthProvider(name string) OauthProvider {
+ provider, ok := oauthProviders[name]
+ if ok {
+ return provider
+ }
+ return nil
+}
diff --git a/mattermost.go b/mattermost.go
index da50a26c3..3d8ab736f 100644
--- a/mattermost.go
+++ b/mattermost.go
@@ -23,8 +23,16 @@ import (
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"github.com/mattermost/platform/web"
+
+ // Plugins
+ _ "github.com/mattermost/platform/model/gitlab"
+
+ // Enterprise Deps
+ _ "github.com/go-ldap/ldap"
)
+//ENTERPRISE_IMPORTS
+
var flagCmdCreateTeam bool
var flagCmdCreateUser bool
var flagCmdAssignRole bool
@@ -51,6 +59,7 @@ func main() {
pwd, _ := os.Getwd()
l4g.Info("Current version is %v (%v/%v/%v)", model.CurrentVersion, model.BuildNumber, model.BuildDate, model.BuildHash)
+ l4g.Info("Enterprise Enabled: %t", model.BuildEnterpriseReady)
l4g.Info("Current working directory is %v", pwd)
l4g.Info("Loaded config file from %v", utils.FindConfigFile(flagConfigFile))
@@ -112,6 +121,7 @@ func runSecurityAndDiagnosticsJobAndForget() {
v.Set(utils.PROP_DIAGNOSTIC_ID, utils.CfgDiagnosticId)
v.Set(utils.PROP_DIAGNOSTIC_BUILD, model.CurrentVersion+"."+model.BuildNumber)
+ v.Set(utils.PROP_DIAGNOSTIC_ENTERPRISE_READY, model.BuildEnterpriseReady)
v.Set(utils.PROP_DIAGNOSTIC_DATABASE, utils.Cfg.SqlSettings.DriverName)
v.Set(utils.PROP_DIAGNOSTIC_OS, runtime.GOOS)
v.Set(utils.PROP_DIAGNOSTIC_CATEGORY, utils.VAL_DIAGNOSTIC_CATEGORY_DEFAULT)
@@ -283,10 +293,6 @@ func cmdCreateUser() {
os.Exit(1)
}
- c := &api.Context{}
- c.RequestId = model.NewId()
- c.IpAddress = "cmd_line"
-
var team *model.Team
user := &model.User{}
user.Email = flagEmail
@@ -302,10 +308,10 @@ func cmdCreateUser() {
user.TeamId = team.Id
}
- api.CreateUser(c, team, user)
- if c.Err != nil {
- if c.Err.Message != "An account with that email already exists." {
- l4g.Error("%v", c.Err)
+ _, err := api.CreateUser(team, user)
+ if err != nil {
+ if err.Message != "An account with that email already exists." {
+ l4g.Error("%v", err)
flushLogAndExit(1)
}
}
@@ -320,6 +326,7 @@ func cmdVersion() {
fmt.Fprintln(os.Stderr, "Build Number: "+model.BuildNumber)
fmt.Fprintln(os.Stderr, "Build Date: "+model.BuildDate)
fmt.Fprintln(os.Stderr, "Build Hash: "+model.BuildHash)
+ fmt.Fprintln(os.Stderr, "Build Enterprise Ready: "+model.BuildEnterpriseReady)
os.Exit(0)
}
diff --git a/model/config.go b/model/config.go
index 06cb9829e..38ef81a85 100644
--- a/model/config.go
+++ b/model/config.go
@@ -20,6 +20,7 @@ const (
DATABASE_DRIVER_POSTGRES = "postgres"
SERVICE_GITLAB = "gitlab"
+ SERVICE_GOOGLE = "google"
)
type ServiceSettings struct {
@@ -133,6 +134,26 @@ type TeamSettings struct {
EnableTeamListing *bool
}
+type LdapSettings struct {
+ // Basic
+ Enable *bool
+ LdapServer *string
+ LdapPort *int
+ BaseDN *string
+ BindUsername *string
+ BindPassword *string
+
+ // User Mapping
+ FirstNameAttribute *string
+ LastNameAttribute *string
+ EmailAttribute *string
+ UsernameAttribute *string
+ IdAttribute *string
+
+ // Advansed
+ QueryTimeout *int
+}
+
type Config struct {
ServiceSettings ServiceSettings
TeamSettings TeamSettings
@@ -144,6 +165,8 @@ type Config struct {
PrivacySettings PrivacySettings
SupportSettings SupportSettings
GitLabSettings SSOSettings
+ GoogleSettings SSOSettings
+ LdapSettings LdapSettings
}
func (o *Config) ToJson() string {
@@ -156,8 +179,11 @@ func (o *Config) ToJson() string {
}
func (o *Config) GetSSOService(service string) *SSOSettings {
- if service == SERVICE_GITLAB {
+ switch service {
+ case SERVICE_GITLAB:
return &o.GitLabSettings
+ case SERVICE_GOOGLE:
+ return &o.GoogleSettings
}
return nil
@@ -251,6 +277,21 @@ func (o *Config) SetDefaults() {
o.SupportSettings.SupportEmail = new(string)
*o.SupportSettings.SupportEmail = "feedback@mattermost.com"
}
+
+ if o.LdapSettings.LdapPort == nil {
+ o.LdapSettings.LdapPort = new(int)
+ *o.LdapSettings.LdapPort = 389
+ }
+
+ if o.LdapSettings.QueryTimeout == nil {
+ o.LdapSettings.QueryTimeout = new(int)
+ *o.LdapSettings.QueryTimeout = 60
+ }
+
+ if o.LdapSettings.Enable == nil {
+ o.LdapSettings.Enable = new(bool)
+ *o.LdapSettings.Enable = false
+ }
}
func (o *Config) IsValid() *AppError {
diff --git a/model/gitlab.go b/model/gitlab/gitlab.go
index 2a8756807..8b96c64f6 100644
--- a/model/gitlab.go
+++ b/model/gitlab/gitlab.go
@@ -1,10 +1,12 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-package model
+package oauthgitlab
import (
"encoding/json"
+ "github.com/mattermost/platform/einterfaces"
+ "github.com/mattermost/platform/model"
"io"
"strconv"
"strings"
@@ -14,6 +16,9 @@ const (
USER_AUTH_SERVICE_GITLAB = "gitlab"
)
+type GitLabProvider struct {
+}
+
type GitLabUser struct {
Id int64 `json:"id"`
Username string `json:"username"`
@@ -22,13 +27,18 @@ type GitLabUser struct {
Name string `json:"name"`
}
-func UserFromGitLabUser(glu *GitLabUser) *User {
- user := &User{}
+func init() {
+ provider := &GitLabProvider{}
+ einterfaces.RegisterOauthProvider(USER_AUTH_SERVICE_GITLAB, provider)
+}
+
+func userFromGitLabUser(glu *GitLabUser) *model.User {
+ user := &model.User{}
username := glu.Username
if username == "" {
username = glu.Login
}
- user.Username = CleanUsername(username)
+ user.Username = model.CleanUsername(username)
splitName := strings.Split(glu.Name, " ")
if len(splitName) == 2 {
user.FirstName = splitName[0]
@@ -46,7 +56,7 @@ func UserFromGitLabUser(glu *GitLabUser) *User {
return user
}
-func GitLabUserFromJson(data io.Reader) *GitLabUser {
+func gitLabUserFromJson(data io.Reader) *GitLabUser {
decoder := json.NewDecoder(data)
var glu GitLabUser
err := decoder.Decode(&glu)
@@ -57,6 +67,18 @@ func GitLabUserFromJson(data io.Reader) *GitLabUser {
}
}
-func (glu *GitLabUser) GetAuthData() string {
+func (glu *GitLabUser) getAuthData() string {
return strconv.FormatInt(glu.Id, 10)
}
+
+func (m *GitLabProvider) GetIdentifier() string {
+ return USER_AUTH_SERVICE_GITLAB
+}
+
+func (m *GitLabProvider) GetUserFromJson(data io.Reader) *model.User {
+ return userFromGitLabUser(gitLabUserFromJson(data))
+}
+
+func (m *GitLabProvider) GetAuthDataFromJson(data io.Reader) string {
+ return gitLabUserFromJson(data).getAuthData()
+}
diff --git a/model/version.go b/model/version.go
index 5e41a28d1..142ddb371 100644
--- a/model/version.go
+++ b/model/version.go
@@ -27,6 +27,7 @@ var CurrentVersion string = versions[0]
var BuildNumber = "_BUILD_NUMBER_"
var BuildDate = "_BUILD_DATE_"
var BuildHash = "_BUILD_HASH_"
+var BuildEnterpriseReady = "_BUILD_ENTERPRISE_READY_"
func SplitVersion(version string) (int64, int64, int64) {
parts := strings.Split(version, ".")
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index d19135b64..82a2ccd05 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -540,7 +540,7 @@ func (us SqlUserStore) GetTotalActiveUsersCount() StoreChannel {
go func() {
result := StoreResult{}
- time := model.GetMillis() - (1000 * 60 * 60 * 12)
+ time := model.GetMillis() - (1000 * 60 * 60 * 24)
if count, err := us.GetReplica().SelectInt("SELECT COUNT(Id) FROM Users WHERE LastActivityAt > :Time", map[string]interface{}{"Time": time}); err != nil {
result.Err = model.NewAppError("SqlUserStore.GetTotalActiveUsersCount", "We could not count the users", err.Error())
diff --git a/utils/config.go b/utils/config.go
index 3f451b88a..18bd15241 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -180,6 +180,7 @@ func getClientConfig(c *model.Config) map[string]string {
props["BuildNumber"] = model.BuildNumber
props["BuildDate"] = model.BuildDate
props["BuildHash"] = model.BuildHash
+ props["BuildEnterpriseReady"] = model.BuildEnterpriseReady
props["SiteName"] = c.TeamSettings.SiteName
props["EnableTeamCreation"] = strconv.FormatBool(c.TeamSettings.EnableTeamCreation)
@@ -203,6 +204,7 @@ func getClientConfig(c *model.Config) map[string]string {
props["FeedbackEmail"] = c.EmailSettings.FeedbackEmail
props["EnableSignUpWithGitLab"] = strconv.FormatBool(c.GitLabSettings.Enable)
+ props["EnableSignUpWithGoogle"] = strconv.FormatBool(c.GoogleSettings.Enable)
props["ShowEmailAddress"] = strconv.FormatBool(c.PrivacySettings.ShowEmailAddress)
@@ -217,5 +219,7 @@ func getClientConfig(c *model.Config) map[string]string {
props["ProfileHeight"] = fmt.Sprintf("%v", c.FileSettings.ProfileHeight)
props["ProfileWidth"] = fmt.Sprintf("%v", c.FileSettings.ProfileWidth)
+ props["EnableLdap"] = strconv.FormatBool(*c.LdapSettings.Enable)
+
return props
}
diff --git a/utils/diagnostic.go b/utils/diagnostic.go
index fd24c2c25..ee199cb35 100644
--- a/utils/diagnostic.go
+++ b/utils/diagnostic.go
@@ -15,6 +15,7 @@ const (
PROP_DIAGNOSTIC_CATEGORY = "c"
VAL_DIAGNOSTIC_CATEGORY_DEFAULT = "d"
PROP_DIAGNOSTIC_BUILD = "b"
+ PROP_DIAGNOSTIC_ENTERPRISE_READY = "be"
PROP_DIAGNOSTIC_DATABASE = "db"
PROP_DIAGNOSTIC_OS = "os"
PROP_DIAGNOSTIC_USER_COUNT = "uc"
diff --git a/web/react/components/about_build_modal.jsx b/web/react/components/about_build_modal.jsx
index 6962876d4..f71e1c9ab 100644
--- a/web/react/components/about_build_modal.jsx
+++ b/web/react/components/about_build_modal.jsx
@@ -33,10 +33,14 @@ export default class AboutBuildModal extends React.Component {
<div className='col-sm-3 info__label'>{'Build Date:'}</div>
<div className='col-sm-9'>{config.BuildDate}</div>
</div>
- <div className='row'>
+ <div className='row form-group'>
<div className='col-sm-3 info__label'>{'Build Hash:'}</div>
<div className='col-sm-9'>{config.BuildHash}</div>
</div>
+ <div className='row'>
+ <div className='col-sm-3 info__label'>{'Enterprise Ready:'}</div>
+ <div className='col-sm-9'>{config.BuildEnterpriseReady}</div>
+ </div>
</Modal.Body>
<Modal.Footer>
<button
@@ -59,4 +63,4 @@ AboutBuildModal.defaultProps = {
AboutBuildModal.propTypes = {
show: React.PropTypes.bool.isRequired,
onModalDismissed: React.PropTypes.func.isRequired
-}; \ No newline at end of file
+};
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index e587c4f84..32b2e9bb7 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -21,6 +21,7 @@ import ServiceSettingsTab from './service_settings.jsx';
import LegalAndSupportSettingsTab from './legal_and_support_settings.jsx';
import TeamUsersTab from './team_users.jsx';
import TeamAnalyticsTab from './team_analytics.jsx';
+import LdapSettingsTab from './ldap_settings.jsx';
export default class AdminController extends React.Component {
constructor(props) {
@@ -151,6 +152,8 @@ export default class AdminController extends React.Component {
tab = <ServiceSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'legal_and_support_settings') {
tab = <LegalAndSupportSettingsTab config={this.state.config} />;
+ } else if (this.state.selected === 'ldap_settings') {
+ tab = <LdapSettingsTab config={this.state.config} />;
} else if (this.state.selected === 'team_users') {
if (this.state.teams) {
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index da445da37..587fb35ed 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -255,6 +255,15 @@ export default class AdminSidebar extends React.Component {
<li>
<a
href='#'
+ className={this.isSelected('ldap_settings')}
+ onClick={this.handleClick.bind(this, 'ldap_settings', null)}
+ >
+ {'LDAP Settings'}
+ </a>
+ </li>
+ <li>
+ <a
+ href='#'
className={this.isSelected('legal_and_support_settings')}
onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)}
>
@@ -334,4 +343,4 @@ AdminSidebar.propTypes = {
selected: React.PropTypes.string,
selectedTeam: React.PropTypes.string,
selectTab: React.PropTypes.func
-}; \ No newline at end of file
+};
diff --git a/web/react/components/admin_console/ldap_settings.jsx b/web/react/components/admin_console/ldap_settings.jsx
new file mode 100644
index 000000000..f8ea62192
--- /dev/null
+++ b/web/react/components/admin_console/ldap_settings.jsx
@@ -0,0 +1,388 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Client from '../../utils/client.jsx';
+import * as AsyncClient from '../../utils/async_client.jsx';
+
+const DEFAULT_LDAP_PORT = 389;
+const DEFAULT_QUERY_TIMEOUT = 60;
+
+export default class LdapSettings extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleEnable = this.handleEnable.bind(this);
+ this.handleDisable = this.handleDisable.bind(this);
+
+ this.state = {
+ saveNeeded: false,
+ serverError: null,
+ enable: this.props.config.LdapSettings.Enable
+ };
+ }
+ handleChange() {
+ this.setState({saveNeeded: true});
+ }
+ handleEnable() {
+ this.setState({saveNeeded: true, enable: true});
+ }
+ handleDisable() {
+ this.setState({saveNeeded: true, enable: false});
+ }
+ handleSubmit(e) {
+ e.preventDefault();
+ $('#save-button').button('loading');
+
+ const config = this.props.config;
+ config.LdapSettings.Enable = this.refs.Enable.checked;
+ config.LdapSettings.LdapServer = this.refs.LdapServer.value.trim();
+
+ let LdapPort = DEFAULT_LDAP_PORT;
+ if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.LdapPort).value, 10))) {
+ LdapPort = parseInt(ReactDOM.findDOMNode(this.refs.LdapPort).value, 10);
+ }
+ config.LdapSettings.LdapPort = LdapPort;
+
+ config.LdapSettings.BaseDN = this.refs.BaseDN.value.trim();
+ config.LdapSettings.BindUsername = this.refs.BindUsername.value.trim();
+ config.LdapSettings.BindPassword = this.refs.BindPassword.value.trim();
+ config.LdapSettings.FirstNameAttribute = this.refs.FirstNameAttribute.value.trim();
+ config.LdapSettings.LastNameAttribute = this.refs.LastNameAttribute.value.trim();
+ config.LdapSettings.EmailAttribute = this.refs.EmailAttribute.value.trim();
+ config.LdapSettings.UsernameAttribute = this.refs.UsernameAttribute.value.trim();
+ config.LdapSettings.IdAttribute = this.refs.IdAttribute.value.trim();
+
+ let QueryTimeout = DEFAULT_QUERY_TIMEOUT;
+ if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.QueryTimeout).value, 10))) {
+ QueryTimeout = parseInt(ReactDOM.findDOMNode(this.refs.QueryTimeout).value, 10);
+ }
+ config.LdapSettings.QueryTimeout = QueryTimeout;
+
+ Client.saveConfig(
+ config,
+ () => {
+ AsyncClient.getConfig();
+ this.setState({
+ serverError: null,
+ saveNeeded: false
+ });
+ $('#save-button').button('reset');
+ },
+ (err) => {
+ this.setState({
+ serverError: err.message,
+ saveNeeded: true
+ });
+ $('#save-button').button('reset');
+ }
+ );
+ }
+ render() {
+ let serverError = '';
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
+
+ let saveClass = 'btn';
+ if (this.state.saveNeeded) {
+ saveClass = 'btn btn-primary';
+ }
+
+ return (
+ <div className='wrapper--fixed'>
+ <h3>{'LDAP Settings'}</h3>
+ <form
+ className='form-horizontal'
+ role='form'
+ >
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='Enable'
+ >
+ {'Enable Login With LDAP:'}
+ </label>
+ <div className='col-sm-8'>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='Enable'
+ value='true'
+ ref='Enable'
+ defaultChecked={this.props.config.LdapSettings.Enable}
+ onChange={this.handleEnable}
+ />
+ {'true'}
+ </label>
+ <label className='radio-inline'>
+ <input
+ type='radio'
+ name='Enable'
+ value='false'
+ defaultChecked={!this.props.config.LdapSettings.Enable}
+ onChange={this.handleDisable}
+ />
+ {'false'}
+ </label>
+ <p className='help-text'>{'When true, Mattermost allows login using LDAP'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='LdapServer'
+ >
+ {'LDAP Server:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='LdapServer'
+ ref='LdapServer'
+ placeholder='Ex "10.0.0.23"'
+ defaultValue={this.props.config.LdapSettings.LdapServer}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'The domain or ip address of LDAP server.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='LdapPort'
+ >
+ {'LDAP Port:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='number'
+ className='form-control'
+ id='LdapPort'
+ ref='LdapPort'
+ placeholder='Ex "389"'
+ defaultValue={this.props.config.LdapSettings.LdapPort}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'The port to connect to the LDAP server on. Default is 389.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='BaseDN'
+ >
+ {'BaseDN:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='BaseDN'
+ ref='BaseDN'
+ placeholder='Ex "dc=mydomain,dc=com"'
+ defaultValue={this.props.config.LdapSettings.BaseDN}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'The base dn where mattermost should search for users.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='BindUsername'
+ >
+ {'Bind Username:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='BindUsername'
+ ref='BindUsername'
+ placeholder=''
+ defaultValue={this.props.config.LdapSettings.BindUsername}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'Username of a user with read access to the LDAP server specified.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='BindPassword'
+ >
+ {'Bind Password:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='password'
+ className='form-control'
+ id='BindPassword'
+ ref='BindPassword'
+ placeholder=''
+ defaultValue={this.props.config.LdapSettings.BindPassword}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'Password of the user given above.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='FirstNameAttribute'
+ >
+ {'First Name Attrubute'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='FirstNameAttribute'
+ ref='FirstNameAttribute'
+ placeholder='Ex "givenName"'
+ defaultValue={this.props.config.LdapSettings.FirstNameAttribute}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'The first name attribute of entires in the LDAP server.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='LastNameAttribute'
+ >
+ {'Last Name Attribute:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='LastNameAttribute'
+ ref='LastNameAttribute'
+ placeholder='Ex "sn"'
+ defaultValue={this.props.config.LdapSettings.LastNameAttribute}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'The last name attribute of entries in the LDAP server.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='EmailAttribute'
+ >
+ {'Email Attribute:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='EmailAttribute'
+ ref='EmailAttribute'
+ placeholder='Ex "mail"'
+ defaultValue={this.props.config.LdapSettings.EmailAttribute}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'The email attribute of entries in the LDAP server.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='UsernameAttribute'
+ >
+ {'Username Attribute:'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='UsernameAttribute'
+ ref='UsernameAttribute'
+ placeholder='Ex "sAMAccountName"'
+ defaultValue={this.props.config.LdapSettings.UsernameAttribute}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'The attribute of entries in the LDAP server to use for username in Mattermost. May be the same as the ID Attribute.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='IdAttribute'
+ >
+ {'Id Attribute: '}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='text'
+ className='form-control'
+ id='IdAttribute'
+ ref='IdAttribute'
+ placeholder='Ex "sAMAccountName"'
+ defaultValue={this.props.config.LdapSettings.IdAttribute}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'The attribute of entries in the LDAP server to use as a unique identifier. Users will use this to login. Ideally this would be the username they are used to loging in with. May be the same as the username attribute above.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-4'
+ htmlFor='QueryTimeout'
+ >
+ {'Query Timeout (seconds):'}
+ </label>
+ <div className='col-sm-8'>
+ <input
+ type='number'
+ className='form-control'
+ id='QueryTimeout'
+ ref='QueryTimeout'
+ placeholder='Ex "60"'
+ defaultValue={this.props.config.LdapSettings.QueryTimeout}
+ onChange={this.handleChange}
+ disabled={!this.state.enable}
+ />
+ <p className='help-text'>{'The timeout value for queries to the LDAP server. Increase if you are getting timeout errors caused by a slow LDAP server.'}</p>
+ </div>
+ </div>
+ <div className='form-group'>
+ <div className='col-sm-12'>
+ {serverError}
+ <button
+ disabled={!this.state.saveNeeded}
+ type='submit'
+ className={saveClass}
+ onClick={this.handleSubmit}
+ id='save-button'
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'}
+ >
+ {'Save'}
+ </button>
+ </div>
+ </div>
+ </form>
+ </div>
+ );
+ }
+}
+LdapSettings.defaultProps = {
+};
+
+LdapSettings.propTypes = {
+ config: React.PropTypes.object
+};
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index d87bd20ad..9afaa8b0d 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -2,97 +2,19 @@
// See License.txt for license information.
import * as Utils from '../utils/utils.jsx';
-import * as Client from '../utils/client.jsx';
-import UserStore from '../stores/user_store.jsx';
-import BrowserStore from '../stores/browser_store.jsx';
+import LoginEmail from './login_email.jsx';
+import LoginLdap from './login_ldap.jsx';
export default class Login extends React.Component {
constructor(props) {
super(props);
- this.handleSubmit = this.handleSubmit.bind(this);
-
this.state = {};
}
- handleSubmit(e) {
- e.preventDefault();
- var state = {};
-
- const name = this.props.teamName;
- if (!name) {
- state.serverError = 'Bad team name';
- this.setState(state);
- return;
- }
-
- const email = ReactDOM.findDOMNode(this.refs.email).value.trim();
- if (!email) {
- state.serverError = 'An email is required';
- this.setState(state);
- return;
- }
-
- const password = ReactDOM.findDOMNode(this.refs.password).value.trim();
- if (!password) {
- state.serverError = 'A password is required';
- this.setState(state);
- return;
- }
-
- if (!BrowserStore.isLocalStorageSupported()) {
- state.serverError = 'This service requires local storage to be enabled. Please enable it or exit private browsing.';
- this.setState(state);
- return;
- }
-
- state.serverError = '';
- this.setState(state);
-
- Client.loginByEmail(name, email, password,
- () => {
- UserStore.setLastEmail(email);
-
- const redirect = Utils.getUrlParameter('redirect');
- if (redirect) {
- window.location.href = decodeURIComponent(redirect);
- } else {
- window.location.href = '/' + name + '/channels/town-square';
- }
- },
- (err) => {
- if (err.message === 'Login failed because email address has not been verified') {
- window.location.href = '/verify_email?teamname=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email);
- return;
- }
- state.serverError = err.message;
- this.valid = false;
- this.setState(state);
- }
- );
- }
render() {
- let serverError;
- if (this.state.serverError) {
- serverError = <label className='control-label'>{this.state.serverError}</label>;
- }
- let priorEmail = UserStore.getLastEmail();
-
- const emailParam = Utils.getUrlParameter('email');
- if (emailParam) {
- priorEmail = decodeURIComponent(emailParam);
- }
-
const teamDisplayName = this.props.teamDisplayName;
const teamName = this.props.teamName;
- let focusEmail = false;
- let focusPassword = false;
- if (priorEmail === '') {
- focusEmail = true;
- } else {
- focusPassword = true;
- }
-
let loginMessage = [];
if (global.window.mm_config.EnableSignUpWithGitLab === 'true') {
loginMessage.push(
@@ -106,9 +28,16 @@ export default class Login extends React.Component {
);
}
- let errorClass = '';
- if (serverError) {
- errorClass = ' has-error';
+ if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
+ loginMessage.push(
+ <a
+ className='btn btn-custom-login google'
+ href={'/' + teamName + '/login/google'}
+ >
+ <span className='icon' />
+ <span>{'with Google Apps'}</span>
+ </a>
+ );
}
const verifiedParam = Utils.getUrlParameter('verified');
@@ -125,39 +54,9 @@ export default class Login extends React.Component {
let emailSignup;
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
emailSignup = (
- <div className='signup__email-container'>
- <div className={'form-group' + errorClass}>
- <input
- autoFocus={focusEmail}
- type='email'
- className='form-control'
- name='email'
- defaultValue={priorEmail}
- ref='email'
- placeholder='Email'
- spellCheck='false'
- />
- </div>
- <div className={'form-group' + errorClass}>
- <input
- autoFocus={focusPassword}
- type='password'
- className='form-control'
- name='password'
- ref='password'
- placeholder='Password'
- spellCheck='false'
- />
- </div>
- <div className='form-group'>
- <button
- type='submit'
- className='btn btn-primary'
- >
- {'Sign in'}
- </button>
- </div>
- </div>
+ <LoginEmail
+ teamName={this.props.teamName}
+ />
);
}
@@ -211,25 +110,30 @@ export default class Login extends React.Component {
);
}
+ let ldapLogin = null;
+ if (global.window.mm_config.EnableLdap === 'true') {
+ ldapLogin = (
+ <LoginLdap
+ teamName={this.props.teamName}
+ />
+ );
+ }
+
return (
<div className='signup-team__container'>
<h5 className='margin--less'>{'Sign in to:'}</h5>
<h2 className='signup-team__name'>{teamDisplayName}</h2>
<h2 className='signup-team__subdomain'>{'on '}{global.window.mm_config.SiteName}</h2>
- <form onSubmit={this.handleSubmit}>
{verifiedBox}
- <div className={'form-group' + errorClass}>
- {serverError}
- </div>
{loginMessage}
{emailSignup}
+ {ldapLogin}
{userSignUp}
<div className='form-group margin--extra form-group--small'>
<span><a href='/find_team'>{'Find your other teams'}</a></span>
</div>
{forgotPassword}
{teamSignUp}
- </form>
</div>
);
}
diff --git a/web/react/components/login_email.jsx b/web/react/components/login_email.jsx
new file mode 100644
index 000000000..cfe34d1c7
--- /dev/null
+++ b/web/react/components/login_email.jsx
@@ -0,0 +1,137 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../utils/utils.jsx';
+import * as Client from '../utils/client.jsx';
+import UserStore from '../stores/user_store.jsx';
+
+export default class LoginEmail extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = {
+ serverError: ''
+ };
+ }
+ handleSubmit(e) {
+ e.preventDefault();
+ var state = {};
+
+ const name = this.props.teamName;
+ if (!name) {
+ state.serverError = 'Bad team name';
+ this.setState(state);
+ return;
+ }
+
+ const email = this.refs.email.value.trim();
+ if (!email) {
+ state.serverError = 'An email is required';
+ this.setState(state);
+ return;
+ }
+
+ const password = this.refs.password.value.trim();
+ if (!password) {
+ state.serverError = 'A password is required';
+ this.setState(state);
+ return;
+ }
+
+ state.serverError = '';
+ this.setState(state);
+
+ Client.loginByEmail(name, email, password,
+ () => {
+ UserStore.setLastEmail(email);
+
+ const redirect = Utils.getUrlParameter('redirect');
+ if (redirect) {
+ window.location.href = decodeURIComponent(redirect);
+ } else {
+ window.location.href = '/' + name + '/channels/town-square';
+ }
+ },
+ (err) => {
+ if (err.message === 'Login failed because email address has not been verified') {
+ window.location.href = '/verify_email?teamname=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email);
+ return;
+ }
+ state.serverError = err.message;
+ this.valid = false;
+ this.setState(state);
+ }
+ );
+ }
+ render() {
+ let serverError;
+ let errorClass = '';
+ if (this.state.serverError) {
+ serverError = <label className='control-label'>{this.state.serverError}</label>;
+ errorClass = ' has-error';
+ }
+
+ let priorEmail = UserStore.getLastEmail();
+ let focusEmail = false;
+ let focusPassword = false;
+ if (priorEmail === '') {
+ focusEmail = true;
+ } else {
+ focusPassword = true;
+ }
+
+ const emailParam = Utils.getUrlParameter('email');
+ if (emailParam) {
+ priorEmail = decodeURIComponent(emailParam);
+ }
+
+ return (
+ <form onSubmit={this.handleSubmit}>
+ <div className='signup__email-container'>
+ <div className={'form-group' + errorClass}>
+ {serverError}
+ </div>
+ <div className={'form-group' + errorClass}>
+ <input
+ autoFocus={focusEmail}
+ type='email'
+ className='form-control'
+ name='email'
+ defaultValue={priorEmail}
+ ref='email'
+ placeholder='Email'
+ spellCheck='false'
+ />
+ </div>
+ <div className={'form-group' + errorClass}>
+ <input
+ autoFocus={focusPassword}
+ type='password'
+ className='form-control'
+ name='password'
+ ref='password'
+ placeholder='Password'
+ spellCheck='false'
+ />
+ </div>
+ <div className='form-group'>
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ {'Sign in'}
+ </button>
+ </div>
+ </div>
+ </form>
+ );
+ }
+}
+LoginEmail.defaultProps = {
+};
+
+LoginEmail.propTypes = {
+ teamName: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/login_ldap.jsx b/web/react/components/login_ldap.jsx
new file mode 100644
index 000000000..1e0e32f4f
--- /dev/null
+++ b/web/react/components/login_ldap.jsx
@@ -0,0 +1,110 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../utils/utils.jsx';
+import * as Client from '../utils/client.jsx';
+
+export default class LoginLdap extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.state = {
+ serverError: ''
+ };
+ }
+ handleSubmit(e) {
+ e.preventDefault();
+ var state = {};
+
+ const teamName = this.props.teamName;
+ if (!teamName) {
+ state.serverError = 'Bad team name';
+ this.setState(state);
+ return;
+ }
+
+ const id = this.refs.id.value.trim();
+ if (!id) {
+ state.serverError = 'An LDAP ID is required';
+ this.setState(state);
+ return;
+ }
+
+ const password = this.refs.password.value.trim();
+ if (!password) {
+ state.serverError = 'An LDAP password is required';
+ this.setState(state);
+ return;
+ }
+
+ state.serverError = '';
+ this.setState(state);
+
+ Client.loginByLdap(teamName, id, password,
+ () => {
+ const redirect = Utils.getUrlParameter('redirect');
+ if (redirect) {
+ window.location.href = decodeURIComponent(redirect);
+ } else {
+ window.location.href = '/' + teamName + '/channels/town-square';
+ }
+ },
+ (err) => {
+ state.serverError = err.message;
+ this.setState(state);
+ }
+ );
+ }
+ render() {
+ let serverError;
+ let errorClass = '';
+ if (this.state.serverError) {
+ serverError = <label className='control-label'>{this.state.serverError}</label>;
+ errorClass = ' has-error';
+ }
+
+ return (
+ <form onSubmit={this.handleSubmit}>
+ <div className='signup__email-container'>
+ <div className={'form-group' + errorClass}>
+ {serverError}
+ </div>
+ <div className={'form-group' + errorClass}>
+ <input
+ autoFocus={true}
+ className='form-control'
+ ref='id'
+ placeholder='LDAP Username'
+ spellCheck='false'
+ />
+ </div>
+ <div className={'form-group' + errorClass}>
+ <input
+ type='password'
+ className='form-control'
+ ref='password'
+ placeholder='LDAP Password'
+ spellCheck='false'
+ />
+ </div>
+ <div className='form-group'>
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ {'Sign in'}
+ </button>
+ </div>
+ </div>
+ </form>
+ );
+ }
+}
+LoginLdap.defaultProps = {
+};
+
+LoginLdap.propTypes = {
+ teamName: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx
index 0ac837326..0e05bc533 100644
--- a/web/react/components/signup_team.jsx
+++ b/web/react/components/signup_team.jsx
@@ -112,6 +112,13 @@ export default class TeamSignUp extends React.Component {
<SSOSignupPage service={Constants.GITLAB_SERVICE} />
</div>
);
+ } else if (this.state.page === 'google') {
+ return (
+ <div>
+ {teamListing}
+ <SSOSignupPage service={Constants.GOOGLE_SERVICE} />
+ </div>
+ );
}
}
}
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index 2bde78726..df11fe045 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -183,11 +183,23 @@ export default class SignupUserComplete extends React.Component {
href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}
>
<span className='icon' />
- <span>with GitLab</span>
+ <span>{'with GitLab'}</span>
</a>
);
}
+ if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
+ signupMessage.push(
+ <a
+ className='btn btn-custom-login google'
+ href={'/' + this.props.teamName + '/signup/google' + window.location.search}
+ >
+ <span className='icon' />
+ <span>{'with Google'}</span>
+ </a>
+ );
+ }
+
var emailSignup;
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
emailSignup = (
diff --git a/web/react/components/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx
index 0254c8b4e..19b9750b3 100644
--- a/web/react/components/team_signup_choose_auth.jsx
+++ b/web/react/components/team_signup_choose_auth.jsx
@@ -26,6 +26,24 @@ export default class ChooseAuthPage extends React.Component {
);
}
+ if (global.window.mm_config.EnableSignUpWithGoogle === 'true') {
+ buttons.push(
+ <a
+ className='btn btn-custom-login google btn-full'
+ href='#'
+ onClick={
+ (e) => {
+ e.preventDefault();
+ this.props.updatePage('google');
+ }
+ }
+ >
+ <span className='icon' />
+ <span>{'Create new team with Google Apps Account'}</span>
+ </a>
+ );
+ }
+
if (global.window.mm_config.EnableSignUpWithEmail === 'true') {
buttons.push(
<a
diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx
index e3f16efb0..f4b323956 100644
--- a/web/react/components/team_signup_with_sso.jsx
+++ b/web/react/components/team_signup_with_sso.jsx
@@ -88,6 +88,18 @@ export default class SSOSignUpPage extends React.Component {
<span>{'Create team with GitLab Account'}</span>
</a>
);
+ } else if (this.props.service === Constants.GOOGLE_SERVICE) {
+ button = (
+ <a
+ className='btn btn-custom-login google btn-full'
+ href='#'
+ onClick={this.handleSubmit}
+ disabled={disabled}
+ >
+ <span className='icon'/>
+ <span>{'Create team with Google Apps Account'}</span>
+ </a>
+ );
}
return (
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index beabf5227..a12e85f67 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -243,7 +243,7 @@ export function loginByEmail(name, email, password, success, error) {
dataType: 'json',
contentType: 'application/json',
type: 'POST',
- data: JSON.stringify({name: name, email: email, password: password}),
+ data: JSON.stringify({name, email, password}),
success: function onSuccess(data, textStatus, xhr) {
track('api', 'api_users_login_success', data.team_id, 'email', data.email);
BrowserStore.signalLogin();
@@ -258,6 +258,26 @@ export function loginByEmail(name, email, password, success, error) {
});
}
+export function loginByLdap(teamName, id, password, success, error) {
+ $.ajax({
+ url: '/api/v1/users/login_ldap',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify({teamName, id, password}),
+ success: function onSuccess(data, textStatus, xhr) {
+ track('api', 'api_users_loginLdap_success', data.team_id, 'id', id);
+ success(data, textStatus, xhr);
+ },
+ error: function onError(xhr, status, err) {
+ track('api', 'api_users_loginLdap_fail', teamName, 'id', id);
+
+ var e = handleError('loginByLdap', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
export function revokeSession(altId, success, error) {
$.ajax({
url: '/api/v1/users/revoke_session',
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 5f027a409..29c5ecc5d 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -114,6 +114,7 @@ export default {
DEFAULT_CHANNEL: 'town-square',
OFFTOPIC_CHANNEL: 'off-topic',
GITLAB_SERVICE: 'gitlab',
+ GOOGLE_SERVICE: 'google',
EMAIL_SERVICE: 'email',
POST_CHUNK_SIZE: 60,
MAX_POST_CHUNKS: 3,
diff --git a/web/web.go b/web/web.go
index 63544229b..f1e8471b8 100644
--- a/web/web.go
+++ b/web/web.go
@@ -8,6 +8,7 @@ import (
"fmt"
"github.com/gorilla/mux"
"github.com/mattermost/platform/api"
+ "github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
@@ -643,6 +644,12 @@ func signupWithOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
service := params["service"]
teamName := params["team"]
+ if !utils.Cfg.TeamSettings.EnableUserCreation {
+ c.Err = model.NewAppError("signupTeam", "User sign-up is disabled.", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
+
if len(teamName) == 0 {
c.Err = model.NewAppError("signupWithOAuth", "Invalid team name", "team_name="+teamName)
c.Err.StatusCode = http.StatusBadRequest
@@ -699,9 +706,12 @@ func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request)
return
} else {
var user *model.User
- if service == model.USER_AUTH_SERVICE_GITLAB {
- glu := model.GitLabUserFromJson(body)
- user = model.UserFromGitLabUser(glu)
+ provider := einterfaces.GetOauthProvider(service)
+ if provider == nil {
+ c.Err = model.NewAppError("signupCompleteOAuth", service+" oauth not avlailable on this server", "")
+ return
+ } else {
+ user = provider.GetUserFromJson(body)
}
if user == nil {
@@ -744,8 +754,9 @@ func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request)
user.TeamId = team.Id
user.EmailVerified = true
- ruser := api.CreateUser(c, team, user)
- if c.Err != nil {
+ ruser, err := api.CreateUser(team, user)
+ if err != nil {
+ c.Err = err
return
}
@@ -799,9 +810,12 @@ func loginCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request)
return
} else {
authData := ""
- if service == model.USER_AUTH_SERVICE_GITLAB {
- glu := model.GitLabUserFromJson(body)
- authData = glu.GetAuthData()
+ provider := einterfaces.GetOauthProvider(service)
+ if provider == nil {
+ c.Err = model.NewAppError("signupCompleteOAuth", service+" oauth not avlailable on this server", "")
+ return
+ } else {
+ authData = provider.GetAuthDataFromJson(body)
}
if len(authData) == 0 {