From 58358ddd7cd0152bf16a7326e1d595524fb51246 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Tue, 8 Dec 2015 13:38:43 -0500 Subject: Some refactoring --- Godeps/Godeps.json | 10 + .../src/github.com/go-ldap/ldap/.gitignore | 0 .../src/github.com/go-ldap/ldap/.travis.yml | 15 + .../_workspace/src/github.com/go-ldap/ldap/LICENSE | 27 ++ .../src/github.com/go-ldap/ldap/README.md | 55 +++ .../_workspace/src/github.com/go-ldap/ldap/add.go | 104 +++++ .../_workspace/src/github.com/go-ldap/ldap/bind.go | 135 ++++++ .../src/github.com/go-ldap/ldap/client.go | 23 + .../src/github.com/go-ldap/ldap/compare.go | 85 ++++ .../_workspace/src/github.com/go-ldap/ldap/conn.go | 369 +++++++++++++++ .../src/github.com/go-ldap/ldap/control.go | 332 ++++++++++++++ .../src/github.com/go-ldap/ldap/debug.go | 24 + .../_workspace/src/github.com/go-ldap/ldap/del.go | 79 ++++ .../_workspace/src/github.com/go-ldap/ldap/dn.go | 155 +++++++ .../src/github.com/go-ldap/ldap/dn_test.go | 70 +++ .../_workspace/src/github.com/go-ldap/ldap/doc.go | 4 + .../src/github.com/go-ldap/ldap/error.go | 137 ++++++ .../src/github.com/go-ldap/ldap/example_test.go | 305 +++++++++++++ .../src/github.com/go-ldap/ldap/filter.go | 456 +++++++++++++++++++ .../src/github.com/go-ldap/ldap/filter_test.go | 248 ++++++++++ .../_workspace/src/github.com/go-ldap/ldap/ldap.go | 286 ++++++++++++ .../src/github.com/go-ldap/ldap/ldap_test.go | 249 ++++++++++ .../src/github.com/go-ldap/ldap/modify.go | 156 +++++++ .../src/github.com/go-ldap/ldap/passwdmodify.go | 137 ++++++ .../src/github.com/go-ldap/ldap/search.go | 403 ++++++++++++++++ .../src/github.com/go-ldap/ldap/search_test.go | 31 ++ .../src/gopkg.in/asn1-ber.v1/.travis.yml | 15 + Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE | 27 ++ .../_workspace/src/gopkg.in/asn1-ber.v1/README.md | 24 + Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go | 504 +++++++++++++++++++++ .../src/gopkg.in/asn1-ber.v1/ber_test.go | 168 +++++++ .../src/gopkg.in/asn1-ber.v1/content_int.go | 25 + .../_workspace/src/gopkg.in/asn1-ber.v1/header.go | 29 ++ .../src/gopkg.in/asn1-ber.v1/header_test.go | 135 ++++++ .../src/gopkg.in/asn1-ber.v1/identifier.go | 103 +++++ .../src/gopkg.in/asn1-ber.v1/identifier_test.go | 344 ++++++++++++++ .../_workspace/src/gopkg.in/asn1-ber.v1/length.go | 71 +++ .../src/gopkg.in/asn1-ber.v1/length_test.go | 158 +++++++ .../src/gopkg.in/asn1-ber.v1/suite_test.go | 182 ++++++++ .../src/gopkg.in/asn1-ber.v1/tests/tc1.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc10.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc11.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc12.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc13.ber | Bin 0 -> 11 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc14.ber | Bin 0 -> 7 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc15.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc16.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc17.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc18.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc19.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc2.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc20.ber | Bin 0 -> 11 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc21.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc22.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc23.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc24.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc25.ber | Bin 0 -> 5 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc26.ber | Bin 0 -> 5 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc27.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc28.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc29.ber | Bin 0 -> 3 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc3.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc30.ber | Bin 0 -> 5 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc31.ber | Bin 0 -> 4 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc32.ber | Bin 0 -> 2 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc33.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc34.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc35.ber | Bin 0 -> 16 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc36.ber | Bin 0 -> 20 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc37.ber | Bin 0 -> 14 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc38.ber | Bin 0 -> 16 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc39.ber | Bin 0 -> 2 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc4.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc40.ber | Bin 0 -> 2 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc41.ber | Bin 0 -> 16 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc42.ber | Bin 0 -> 14 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc43.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc44.ber | Bin 0 -> 2 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc45.ber | Bin 0 -> 2 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc46.ber | Bin 0 -> 11 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc47.ber | Bin 0 -> 16 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc48.ber | Bin 0 -> 16 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc5.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc6.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc7.ber | 1 + .../src/gopkg.in/asn1-ber.v1/tests/tc8.ber | Bin 0 -> 5 bytes .../src/gopkg.in/asn1-ber.v1/tests/tc9.ber | 1 + Godeps/_workspace/src/gopkg.in/asn1-ber.v1/util.go | 24 + Makefile | 31 ++ api/team.go | 5 +- api/user.go | 100 +++- einterfaces/ldap.go | 22 + einterfaces/oauthproviders.go | 29 ++ mattermost.go | 23 +- model/config.go | 43 +- model/gitlab.go | 62 --- model/gitlab/gitlab.go | 84 ++++ model/version.go | 1 + store/sql_user_store.go | 2 +- utils/config.go | 4 + utils/diagnostic.go | 1 + web/react/components/about_build_modal.jsx | 8 +- .../components/admin_console/admin_controller.jsx | 3 + .../components/admin_console/admin_sidebar.jsx | 11 +- .../components/admin_console/ldap_settings.jsx | 388 ++++++++++++++++ web/react/components/login.jsx | 146 +----- web/react/components/login_email.jsx | 137 ++++++ web/react/components/login_ldap.jsx | 110 +++++ web/react/components/signup_team.jsx | 7 + web/react/components/signup_user_complete.jsx | 14 +- web/react/components/team_signup_choose_auth.jsx | 18 + web/react/components/team_signup_with_sso.jsx | 12 + web/react/utils/client.jsx | 22 +- web/react/utils/constants.jsx | 1 + web/web.go | 30 +- 115 files changed, 6817 insertions(+), 226 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/.gitignore create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/LICENSE create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/README.md create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/add.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/bind.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/client.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/compare.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/control.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/debug.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/del.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/doc.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/error.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/modify.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/passwdmodify.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/search.go create mode 100644 Godeps/_workspace/src/github.com/go-ldap/ldap/search_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/content_int.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/header_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/identifier_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/length_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/suite_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc1.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc10.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc11.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc12.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc13.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc14.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc15.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc16.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc17.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc18.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc19.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc2.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc20.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc21.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc22.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc23.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc24.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc25.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc26.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc27.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc28.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc29.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc3.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc30.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc31.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc32.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc33.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc34.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc35.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc36.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc37.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc38.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc39.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc4.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc40.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc41.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc42.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc43.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc44.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc45.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc46.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc47.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc48.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc5.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc6.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc7.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc8.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc9.ber create mode 100644 Godeps/_workspace/src/gopkg.in/asn1-ber.v1/util.go create mode 100644 einterfaces/ldap.go create mode 100644 einterfaces/oauthproviders.go delete mode 100644 model/gitlab.go create mode 100644 model/gitlab/gitlab.go create mode 100644 web/react/components/admin_console/ldap_settings.jsx create mode 100644 web/react/components/login_email.jsx create mode 100644 web/react/components/login_ldap.jsx diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 52b7d5e92..bd2392f90 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -29,6 +29,11 @@ "Comment": "v1.7-148-gc0e2e1a", "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", @@ -113,6 +118,11 @@ "ImportPath": "golang.org/x/image/tiff", "Rev": "baddd3465a05d84a6d8d3507547a91cb188c81ea" }, + { + "ImportPath": "gopkg.in/asn1-ber.v1", + "Comment": "v1.1", + "Rev": "4e86f4367175e39f69d9358a5f17b4dda270378d" + }, { "ImportPath": "gopkg.in/fsnotify.v1", "Comment": "v1.2.5", 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 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 +// -- [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 , 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 , , , , +// , , , , , , , , +// , , and 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 +// -- [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 +// -- 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<= 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc13.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc14.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc20.ber 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 @@ +`HO Jc/ \ 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc25.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc26.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc29.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc30.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc31.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc32.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc35.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc36.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc37.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc38.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc39.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc40.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc41.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc42.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc44.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc45.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc46.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc47.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc48.ber 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 Binary files /dev/null and b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/tests/tc8.ber 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.go deleted file mode 100644 index 2a8756807..000000000 --- a/model/gitlab.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package model - -import ( - "encoding/json" - "io" - "strconv" - "strings" -) - -const ( - USER_AUTH_SERVICE_GITLAB = "gitlab" -) - -type GitLabUser struct { - Id int64 `json:"id"` - Username string `json:"username"` - Login string `json:"login"` - Email string `json:"email"` - Name string `json:"name"` -} - -func UserFromGitLabUser(glu *GitLabUser) *User { - user := &User{} - username := glu.Username - if username == "" { - username = glu.Login - } - user.Username = CleanUsername(username) - splitName := strings.Split(glu.Name, " ") - if len(splitName) == 2 { - user.FirstName = splitName[0] - user.LastName = splitName[1] - } else if len(splitName) >= 2 { - user.FirstName = splitName[0] - user.LastName = strings.Join(splitName[1:], " ") - } else { - user.FirstName = glu.Name - } - user.Email = glu.Email - user.AuthData = strconv.FormatInt(glu.Id, 10) - user.AuthService = USER_AUTH_SERVICE_GITLAB - - return user -} - -func GitLabUserFromJson(data io.Reader) *GitLabUser { - decoder := json.NewDecoder(data) - var glu GitLabUser - err := decoder.Decode(&glu) - if err == nil { - return &glu - } else { - return nil - } -} - -func (glu *GitLabUser) GetAuthData() string { - return strconv.FormatInt(glu.Id, 10) -} diff --git a/model/gitlab/gitlab.go b/model/gitlab/gitlab.go new file mode 100644 index 000000000..8b96c64f6 --- /dev/null +++ b/model/gitlab/gitlab.go @@ -0,0 +1,84 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package oauthgitlab + +import ( + "encoding/json" + "github.com/mattermost/platform/einterfaces" + "github.com/mattermost/platform/model" + "io" + "strconv" + "strings" +) + +const ( + USER_AUTH_SERVICE_GITLAB = "gitlab" +) + +type GitLabProvider struct { +} + +type GitLabUser struct { + Id int64 `json:"id"` + Username string `json:"username"` + Login string `json:"login"` + Email string `json:"email"` + Name string `json:"name"` +} + +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 = model.CleanUsername(username) + splitName := strings.Split(glu.Name, " ") + if len(splitName) == 2 { + user.FirstName = splitName[0] + user.LastName = splitName[1] + } else if len(splitName) >= 2 { + user.FirstName = splitName[0] + user.LastName = strings.Join(splitName[1:], " ") + } else { + user.FirstName = glu.Name + } + user.Email = glu.Email + user.AuthData = strconv.FormatInt(glu.Id, 10) + user.AuthService = USER_AUTH_SERVICE_GITLAB + + return user +} + +func gitLabUserFromJson(data io.Reader) *GitLabUser { + decoder := json.NewDecoder(data) + var glu GitLabUser + err := decoder.Decode(&glu) + if err == nil { + return &glu + } else { + return nil + } +} + +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 {
{'Build Date:'}
{config.BuildDate}
-
+
{'Build Hash:'}
{config.BuildHash}
+
+
{'Enterprise Ready:'}
+
{config.BuildEnterpriseReady}
+
+
+ + + + ); + } +} +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 = ; - } - 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( + + + {'with Google Apps'} + + ); } 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 = ( -
-
- -
-
- -
-
- -
-
+ ); } @@ -211,25 +110,30 @@ export default class Login extends React.Component { ); } + let ldapLogin = null; + if (global.window.mm_config.EnableLdap === 'true') { + ldapLogin = ( + + ); + } + return (
{'Sign in to:'}

{teamDisplayName}

{'on '}{global.window.mm_config.SiteName}

-
{verifiedBox} -
- {serverError} -
{loginMessage} {emailSignup} + {ldapLogin} {userSignUp} {forgotPassword} {teamSignUp} -
); } 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 = ; + 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 ( +
+
+
+ {serverError} +
+
+ +
+
+ +
+
+ +
+
+
+ ); + } +} +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 = ; + errorClass = ' has-error'; + } + + return ( +
+
+
+ {serverError} +
+
+ +
+
+ +
+
+ +
+
+
+ ); + } +} +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 { ); + } else if (this.state.page === 'google') { + return ( +
+ {teamListing} + +
+ ); } } } 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} > - with GitLab + {'with GitLab'} ); } + if (global.window.mm_config.EnableSignUpWithGoogle === 'true') { + signupMessage.push( + + + {'with Google'} + + ); + } + 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( + { + e.preventDefault(); + this.props.updatePage('google'); + } + } + > + + {'Create new team with Google Apps Account'} + + ); + } + if (global.window.mm_config.EnableSignUpWithEmail === 'true') { buttons.push( {'Create team with GitLab Account'} ); + } else if (this.props.service === Constants.GOOGLE_SERVICE) { + button = ( + + + {'Create team with Google Apps Account'} + + ); } 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 { -- cgit v1.2.3-1-g7c22