From ea993b64046ce46198580aba27a313e1f617bc04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 15 May 2015 09:37:16 +0200 Subject: [PATCH 001/417] Some inital ldap work --- pkg/api/ldap/ldap.go | 0 pkg/components/ldap/LICENSE | 27 ++ pkg/components/ldap/README | 33 ++ pkg/components/ldap/_examples/enterprise.ldif | 63 ++++ pkg/components/ldap/_examples/modify.go | 89 +++++ pkg/components/ldap/_examples/search.go | 52 +++ pkg/components/ldap/_examples/searchSSL.go | 45 +++ pkg/components/ldap/_examples/searchTLS.go | 45 +++ pkg/components/ldap/_examples/slapd.conf | 67 ++++ pkg/components/ldap/bind.go | 55 +++ pkg/components/ldap/conn.go | 275 ++++++++++++++ pkg/components/ldap/control.go | 157 ++++++++ pkg/components/ldap/debug.go | 24 ++ pkg/components/ldap/filter.go | 248 +++++++++++++ pkg/components/ldap/filter_test.go | 78 ++++ pkg/components/ldap/ldap.go | 302 +++++++++++++++ pkg/components/ldap/ldap_test.go | 123 ++++++ pkg/components/ldap/modify.go | 156 ++++++++ pkg/components/ldap/search.go | 350 ++++++++++++++++++ 19 files changed, 2189 insertions(+) create mode 100644 pkg/api/ldap/ldap.go create mode 100644 pkg/components/ldap/LICENSE create mode 100644 pkg/components/ldap/README create mode 100644 pkg/components/ldap/_examples/enterprise.ldif create mode 100644 pkg/components/ldap/_examples/modify.go create mode 100644 pkg/components/ldap/_examples/search.go create mode 100644 pkg/components/ldap/_examples/searchSSL.go create mode 100644 pkg/components/ldap/_examples/searchTLS.go create mode 100644 pkg/components/ldap/_examples/slapd.conf create mode 100644 pkg/components/ldap/bind.go create mode 100644 pkg/components/ldap/conn.go create mode 100644 pkg/components/ldap/control.go create mode 100644 pkg/components/ldap/debug.go create mode 100644 pkg/components/ldap/filter.go create mode 100644 pkg/components/ldap/filter_test.go create mode 100644 pkg/components/ldap/ldap.go create mode 100644 pkg/components/ldap/ldap_test.go create mode 100644 pkg/components/ldap/modify.go create mode 100644 pkg/components/ldap/search.go diff --git a/pkg/api/ldap/ldap.go b/pkg/api/ldap/ldap.go new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pkg/components/ldap/LICENSE b/pkg/components/ldap/LICENSE new file mode 100644 index 00000000000..74487567632 --- /dev/null +++ b/pkg/components/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/pkg/components/ldap/README b/pkg/components/ldap/README new file mode 100644 index 00000000000..edb54de0ac5 --- /dev/null +++ b/pkg/components/ldap/README @@ -0,0 +1,33 @@ +Basic LDAP v3 functionality for the GO programming language. + +Required Librarys: + github.com/johnweldon/asn1-ber + +Working: + Connecting to LDAP server + Binding to LDAP server + Searching for entries + Compiling string filters to LDAP filters + Paging Search Results + Modify Requests / Responses + +Examples: + search + modify + +Tests Implemented: + Filter Compile / Decompile + +TODO: + Add Requests / Responses + Delete Requests / Responses + Modify DN Requests / Responses + Compare Requests / Responses + Implement Tests / Benchmarks + +This feature is disabled at the moment, because in some cases the "Search Request Done" packet will be handled before the last "Search Request Entry": + Mulitple internal goroutines to handle network traffic + Makes library goroutine safe + Can perform multiple search requests at the same time and return + the results to the proper goroutine. All requests are blocking + requests, so the goroutine does not need special handling diff --git a/pkg/components/ldap/_examples/enterprise.ldif b/pkg/components/ldap/_examples/enterprise.ldif new file mode 100644 index 00000000000..f0ec28f16be --- /dev/null +++ b/pkg/components/ldap/_examples/enterprise.ldif @@ -0,0 +1,63 @@ +dn: dc=enterprise,dc=org +objectClass: dcObject +objectClass: organization +o: acme + +dn: cn=admin,dc=enterprise,dc=org +objectClass: person +cn: admin +sn: admin +description: "LDAP Admin" + +dn: ou=crew,dc=enterprise,dc=org +ou: crew +objectClass: organizationalUnit + + +dn: cn=kirkj,ou=crew,dc=enterprise,dc=org +cn: kirkj +sn: Kirk +gn: James Tiberius +mail: james.kirk@enterprise.org +objectClass: inetOrgPerson + +dn: cn=spock,ou=crew,dc=enterprise,dc=org +cn: spock +sn: Spock +mail: spock@enterprise.org +objectClass: inetOrgPerson + +dn: cn=mccoyl,ou=crew,dc=enterprise,dc=org +cn: mccoyl +sn: McCoy +gn: Leonard +mail: leonard.mccoy@enterprise.org +objectClass: inetOrgPerson + +dn: cn=scottm,ou=crew,dc=enterprise,dc=org +cn: scottm +sn: Scott +gn: Montgomery +mail: Montgomery.scott@enterprise.org +objectClass: inetOrgPerson + +dn: cn=uhuran,ou=crew,dc=enterprise,dc=org +cn: uhuran +sn: Uhura +gn: Nyota +mail: nyota.uhura@enterprise.org +objectClass: inetOrgPerson + +dn: cn=suluh,ou=crew,dc=enterprise,dc=org +cn: suluh +sn: Sulu +gn: Hikaru +mail: hikaru.sulu@enterprise.org +objectClass: inetOrgPerson + +dn: cn=chekovp,ou=crew,dc=enterprise,dc=org +cn: chekovp +sn: Chekov +gn: pavel +mail: pavel.chekov@enterprise.org +objectClass: inetOrgPerson diff --git a/pkg/components/ldap/_examples/modify.go b/pkg/components/ldap/_examples/modify.go new file mode 100644 index 00000000000..cd6dfc9eb71 --- /dev/null +++ b/pkg/components/ldap/_examples/modify.go @@ -0,0 +1,89 @@ +// 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. + +package main + +import ( + "errors" + "fmt" + "log" + + "github.com/gogits/gogs/modules/ldap" +) + +var ( + LdapServer string = "localhost" + LdapPort uint16 = 389 + BaseDN string = "dc=enterprise,dc=org" + BindDN string = "cn=admin,dc=enterprise,dc=org" + BindPW string = "enterprise" + Filter string = "(cn=kirkj)" +) + +func search(l *ldap.Conn, filter string, attributes []string) (*ldap.Entry, *ldap.Error) { + search := ldap.NewSearchRequest( + BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + filter, + attributes, + nil) + + sr, err := l.Search(search) + if err != nil { + log.Fatalf("ERROR: %s\n", err) + return nil, err + } + + log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries)) + if len(sr.Entries) == 0 { + return nil, ldap.NewError(ldap.ErrorDebugging, errors.New(fmt.Sprintf("no entries found for: %s", filter))) + } + return sr.Entries[0], nil +} + +func main() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", LdapServer, LdapPort)) + if err != nil { + log.Fatalf("ERROR: %s\n", err.Error()) + } + defer l.Close() + // l.Debug = true + + l.Bind(BindDN, BindPW) + + log.Printf("The Search for Kirk ... %s\n", Filter) + entry, err := search(l, Filter, []string{}) + if err != nil { + log.Fatal("could not get entry") + } + entry.PrettyPrint(0) + + log.Printf("modify the mail address and add a description ... \n") + modify := ldap.NewModifyRequest(entry.DN) + modify.Add("description", []string{"Captain of the USS Enterprise"}) + modify.Replace("mail", []string{"captain@enterprise.org"}) + if err := l.Modify(modify); err != nil { + log.Fatalf("ERROR: %s\n", err.Error()) + } + + entry, err = search(l, Filter, []string{}) + if err != nil { + log.Fatal("could not get entry") + } + entry.PrettyPrint(0) + + log.Printf("reset the entry ... \n") + modify = ldap.NewModifyRequest(entry.DN) + modify.Delete("description", []string{}) + modify.Replace("mail", []string{"james.kirk@enterprise.org"}) + if err := l.Modify(modify); err != nil { + log.Fatalf("ERROR: %s\n", err.Error()) + } + + entry, err = search(l, Filter, []string{}) + if err != nil { + log.Fatal("could not get entry") + } + entry.PrettyPrint(0) +} diff --git a/pkg/components/ldap/_examples/search.go b/pkg/components/ldap/_examples/search.go new file mode 100644 index 00000000000..609256f4d3c --- /dev/null +++ b/pkg/components/ldap/_examples/search.go @@ -0,0 +1,52 @@ +// 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. + +package main + +import ( + "fmt" + "log" + + "github.com/gogits/gogs/modules/ldap" +) + +var ( + ldapServer string = "adserver" + ldapPort uint16 = 3268 + baseDN string = "dc=*,dc=*" + filter string = "(&(objectClass=user)(sAMAccountName=*)(memberOf=CN=*,OU=*,DC=*,DC=*))" + Attributes []string = []string{"memberof"} + user string = "*" + passwd string = "*" +) + +func main() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + log.Fatalf("ERROR: %s\n", err.Error()) + } + defer l.Close() + // l.Debug = true + + err = l.Bind(user, passwd) + if err != nil { + log.Printf("ERROR: Cannot bind: %s\n", err.Error()) + return + } + search := ldap.NewSearchRequest( + baseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + filter, + Attributes, + nil) + + sr, err := l.Search(search) + if err != nil { + log.Fatalf("ERROR: %s\n", err.Error()) + return + } + + log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries)) + sr.PrettyPrint(0) +} diff --git a/pkg/components/ldap/_examples/searchSSL.go b/pkg/components/ldap/_examples/searchSSL.go new file mode 100644 index 00000000000..aa9cbcc1249 --- /dev/null +++ b/pkg/components/ldap/_examples/searchSSL.go @@ -0,0 +1,45 @@ +// 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. + +package main + +import ( + "fmt" + "log" + + "github.com/gogits/gogs/modules/ldap" +) + +var ( + LdapServer string = "localhost" + LdapPort uint16 = 636 + BaseDN string = "dc=enterprise,dc=org" + Filter string = "(cn=kirkj)" + Attributes []string = []string{"mail"} +) + +func main() { + l, err := ldap.DialSSL("tcp", fmt.Sprintf("%s:%d", LdapServer, LdapPort), nil) + if err != nil { + log.Fatalf("ERROR: %s\n", err.String()) + } + defer l.Close() + // l.Debug = true + + search := ldap.NewSearchRequest( + BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + Filter, + Attributes, + nil) + + sr, err := l.Search(search) + if err != nil { + log.Fatalf("ERROR: %s\n", err.String()) + return + } + + log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries)) + sr.PrettyPrint(0) +} diff --git a/pkg/components/ldap/_examples/searchTLS.go b/pkg/components/ldap/_examples/searchTLS.go new file mode 100644 index 00000000000..c771a8eda87 --- /dev/null +++ b/pkg/components/ldap/_examples/searchTLS.go @@ -0,0 +1,45 @@ +// 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. + +package main + +import ( + "fmt" + "log" + + "github.com/gogits/gogs/modules/ldap" +) + +var ( + LdapServer string = "localhost" + LdapPort uint16 = 389 + BaseDN string = "dc=enterprise,dc=org" + Filter string = "(cn=kirkj)" + Attributes []string = []string{"mail"} +) + +func main() { + l, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", LdapServer, LdapPort), nil) + if err != nil { + log.Fatalf("ERROR: %s\n", err.Error()) + } + defer l.Close() + // l.Debug = true + + search := ldap.NewSearchRequest( + BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + Filter, + Attributes, + nil) + + sr, err := l.Search(search) + if err != nil { + log.Fatalf("ERROR: %s\n", err.Error()) + return + } + + log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries)) + sr.PrettyPrint(0) +} diff --git a/pkg/components/ldap/_examples/slapd.conf b/pkg/components/ldap/_examples/slapd.conf new file mode 100644 index 00000000000..5a66be0152d --- /dev/null +++ b/pkg/components/ldap/_examples/slapd.conf @@ -0,0 +1,67 @@ +# +# See slapd.conf(5) for details on configuration options. +# This file should NOT be world readable. +# +include /private/etc/openldap/schema/core.schema +include /private/etc/openldap/schema/cosine.schema +include /private/etc/openldap/schema/inetorgperson.schema + +# Define global ACLs to disable default read access. + +# Do not enable referrals until AFTER you have a working directory +# service AND an understanding of referrals. +#referral ldap://root.openldap.org + +pidfile /private/var/db/openldap/run/slapd.pid +argsfile /private/var/db/openldap/run/slapd.args + +# Load dynamic backend modules: +# modulepath /usr/libexec/openldap +# moduleload back_bdb.la +# moduleload back_hdb.la +# moduleload back_ldap.la + +# Sample security restrictions +# Require integrity protection (prevent hijacking) +# Require 112-bit (3DES or better) encryption for updates +# Require 63-bit encryption for simple bind +# security ssf=1 update_ssf=112 simple_bind=64 + +# Sample access control policy: +# Root DSE: allow anyone to read it +# Subschema (sub)entry DSE: allow anyone to read it +# Other DSEs: +# Allow self write access +# Allow authenticated users read access +# Allow anonymous users to authenticate +# Directives needed to implement policy: +# access to dn.base="" by * read +# access to dn.base="cn=Subschema" by * read +# access to * +# by self write +# by users read +# by anonymous auth +# +# if no access controls are present, the default policy +# allows anyone and everyone to read anything but restricts +# updates to rootdn. (e.g., "access to * by * read") +# +# rootdn can always read and write EVERYTHING! + +####################################################################### +# BDB database definitions +####################################################################### + +database bdb +suffix "dc=enterprise,dc=org" +rootdn "cn=admin,dc=enterprise,dc=org" +# Cleartext passwords, especially for the rootdn, should +# be avoid. See slappasswd(8) and slapd.conf(5) for details. +# Use of strong authentication encouraged. +rootpw {SSHA}laO00HsgszhK1O0Z5qR0/i/US69Osfeu +# The database directory MUST exist prior to running slapd AND +# should only be accessible by the slapd and slap tools. +# Mode 700 recommended. +directory /private/var/db/openldap/openldap-data +# Indices to maintain +index objectClass eq diff --git a/pkg/components/ldap/bind.go b/pkg/components/ldap/bind.go new file mode 100644 index 00000000000..0561e611d1d --- /dev/null +++ b/pkg/components/ldap/bind.go @@ -0,0 +1,55 @@ +// 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" + + "github.com/gogits/gogs/modules/asn1-ber" +) + +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/pkg/components/ldap/conn.go b/pkg/components/ldap/conn.go new file mode 100644 index 00000000000..6a244f1253b --- /dev/null +++ b/pkg/components/ldap/conn.go @@ -0,0 +1,275 @@ +// 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" + "log" + "net" + "sync" + + "github.com/gogits/gogs/modules/asn1-ber" +) + +const ( + MessageQuit = 0 + MessageRequest = 1 + MessageResponse = 2 + MessageFinish = 3 +) + +type messagePacket struct { + Op int + MessageID uint64 + Packet *ber.Packet + Channel chan *ber.Packet +} + +// Conn represents an LDAP Connection +type Conn struct { + conn net.Conn + isTLS bool + isClosing bool + Debug debugging + chanConfirm chan bool + chanResults map[uint64]chan *ber.Packet + chanMessage chan *messagePacket + chanMessageID chan uint64 + wgSender sync.WaitGroup + wgClose sync.WaitGroup + once sync.Once +} + +// 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.Dial(network, addr) + if err != nil { + return nil, NewError(ErrorNetwork, err) + } + conn := NewConn(c) + 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) { + c, err := tls.Dial(network, addr, config) + if err != nil { + return nil, NewError(ErrorNetwork, err) + } + conn := NewConn(c) + conn.isTLS = true + conn.start() + return conn, nil +} + +// NewConn returns a new Conn using conn for network I/O. +func NewConn(conn net.Conn) *Conn { + return &Conn{ + conn: conn, + chanConfirm: make(chan bool), + chanMessageID: make(chan uint64), + chanMessage: make(chan *messagePacket, 10), + chanResults: map[uint64]chan *ber.Packet{}, + } +} + +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.conn = nil + l.wgClose.Done() + }) + l.wgClose.Wait() +} + +// Returns the next available messageID +func (l *Conn) nextMessageID() uint64 { + 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) + + _, err := l.conn.Write(packet.Bytes()) + if err != nil { + return NewError(ErrorNetwork, err) + } + + packet, err = ber.ReadPacket(l.conn) + if err != nil { + return NewError(ErrorNetwork, err) + } + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + return err + } + ber.PrintPacket(packet) + } + + if packet.Children[1].Children[0].Value.(uint64) == 0 { + conn := tls.Client(l.conn, config) + l.isTLS = true + l.conn = conn + } + + return nil +} + +func (l *Conn) sendMessage(packet *ber.Packet) (chan *ber.Packet, error) { + if l.isClosing { + return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed")) + } + out := make(chan *ber.Packet) + message := &messagePacket{ + Op: MessageRequest, + MessageID: packet.Children[0].Value.(uint64), + Packet: packet, + Channel: out, + } + l.sendProcessMessage(message) + return out, nil +} + +func (l *Conn) finishMessage(messageID uint64) { + if l.isClosing { + return + } + 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() { + 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 uint64 = 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() { + defer func() { + l.Close() + }() + + for { + packet, err := ber.ReadPacket(l.conn) + if err != nil { + l.Debug.Printf("reader: %s", err.Error()) + return + } + addLDAPDescriptions(packet) + message := &messagePacket{ + Op: MessageResponse, + MessageID: packet.Children[0].Value.(uint64), + Packet: packet, + } + if !l.sendProcessMessage(message) { + return + } + + } +} diff --git a/pkg/components/ldap/control.go b/pkg/components/ldap/control.go new file mode 100644 index 00000000000..4b15f1bd4a8 --- /dev/null +++ b/pkg/components/ldap/control.go @@ -0,0 +1,157 @@ +// 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" + + "github.com/gogits/gogs/modules/asn1-ber" +) + +const ( + ControlTypePaging = "1.2.840.113556.1.4.319" +) + +var ControlTypeMap = map[string]string{ + ControlTypePaging: "Paging", +} + +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, 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 +} + +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.(uint64)) + c.Cookie = value.Children[1].Data.Bytes() + value.Children[1].Value = c.Cookie + 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 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/pkg/components/ldap/debug.go b/pkg/components/ldap/debug.go new file mode 100644 index 00000000000..67856fe7a60 --- /dev/null +++ b/pkg/components/ldap/debug.go @@ -0,0 +1,24 @@ +package ldap + +import ( + "log" + + "github.com/gogits/gogs/modules/asn1-ber" +) + +// debugging 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/pkg/components/ldap/filter.go b/pkg/components/ldap/filter.go new file mode 100644 index 00000000000..0ad7a403bca --- /dev/null +++ b/pkg/components/ldap/filter.go @@ -0,0 +1,248 @@ +// 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" + "fmt" + + "github.com/gogits/gogs/modules/asn1-ber" +) + +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", +} + +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 += "=" + switch packet.Children[1].Children[0].Tag { + case FilterSubstringsInitial: + ret += ber.DecodeString(packet.Children[1].Children[0].Data.Bytes()) + "*" + case FilterSubstringsAny: + ret += "*" + ber.DecodeString(packet.Children[1].Children[0].Data.Bytes()) + "*" + case FilterSubstringsFinal: + ret += "*" + ber.DecodeString(packet.Children[1].Children[0].Data.Bytes()) + } + case FilterEqualityMatch: + ret += ber.DecodeString(packet.Children[0].Data.Bytes()) + ret += "=" + ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + case FilterGreaterOrEqual: + ret += ber.DecodeString(packet.Children[0].Data.Bytes()) + ret += ">=" + ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + case FilterLessOrEqual: + ret += ber.DecodeString(packet.Children[0].Data.Bytes()) + ret += "<=" + ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + case FilterPresent: + ret += ber.DecodeString(packet.Children[0].Data.Bytes()) + ret += "=*" + case FilterApproxMatch: + ret += ber.DecodeString(packet.Children[0].Data.Bytes()) + ret += "~=" + ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + } + + 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 + var err error + + defer func() { + if r := recover(); r != nil { + err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter")) + } + }() + + newPos := pos + switch filter[pos] { + case '(': + packet, newPos, err = compileFilter(filter, pos+1) + newPos++ + return packet, newPos, err + case '&': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd]) + newPos, err = compileFilterSet(filter, pos+1, packet) + return packet, newPos, err + case '|': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr]) + newPos, err = compileFilterSet(filter, pos+1, 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+1) + packet.AppendChild(child) + return packet, newPos, err + default: + attribute := "" + condition := "" + for newPos < len(filter) && filter[newPos] != ')' { + switch { + case packet != nil: + condition += fmt.Sprintf("%c", filter[newPos]) + case filter[newPos] == '=': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch]) + case filter[newPos] == '>' && filter[newPos+1] == '=': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual]) + newPos++ + case filter[newPos] == '<' && filter[newPos+1] == '=': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual]) + newPos++ + case filter[newPos] == '~' && filter[newPos+1] == '=': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterLessOrEqual]) + newPos++ + case packet == nil: + attribute += fmt.Sprintf("%c", filter[newPos]) + } + newPos++ + } + 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 + } + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute")) + switch { + case packet.Tag == FilterEqualityMatch && condition == "*": + packet.Tag = FilterPresent + packet.Description = FilterMap[uint64(packet.Tag)] + case packet.Tag == FilterEqualityMatch && condition[0] == '*' && condition[len(condition)-1] == '*': + // Any + packet.Tag = FilterSubstrings + packet.Description = FilterMap[uint64(packet.Tag)] + seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings") + seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterSubstringsAny, condition[1:len(condition)-1], "Any Substring")) + packet.AppendChild(seq) + case packet.Tag == FilterEqualityMatch && condition[0] == '*': + // Final + packet.Tag = FilterSubstrings + packet.Description = FilterMap[uint64(packet.Tag)] + seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings") + seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterSubstringsFinal, condition[1:], "Final Substring")) + packet.AppendChild(seq) + case packet.Tag == FilterEqualityMatch && condition[len(condition)-1] == '*': + // Initial + packet.Tag = FilterSubstrings + packet.Description = FilterMap[uint64(packet.Tag)] + seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings") + seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterSubstringsInitial, condition[:len(condition)-1], "Initial Substring")) + packet.AppendChild(seq) + default: + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, condition, "Condition")) + } + newPos++ + return packet, newPos, err + } +} diff --git a/pkg/components/ldap/filter_test.go b/pkg/components/ldap/filter_test.go new file mode 100644 index 00000000000..761ff42fd51 --- /dev/null +++ b/pkg/components/ldap/filter_test.go @@ -0,0 +1,78 @@ +package ldap + +import ( + "testing" + + "github.com/gogits/gogs/modules/asn1-ber" +) + +type compileTest struct { + filterStr string + filterType int +} + +var testFilters = []compileTest{ + compileTest{filterStr: "(&(sn=Miller)(givenName=Bob))", filterType: FilterAnd}, + compileTest{filterStr: "(|(sn=Miller)(givenName=Bob))", filterType: FilterOr}, + compileTest{filterStr: "(!(sn=Miller))", filterType: FilterNot}, + compileTest{filterStr: "(sn=Miller)", filterType: FilterEqualityMatch}, + compileTest{filterStr: "(sn=Mill*)", filterType: FilterSubstrings}, + compileTest{filterStr: "(sn=*Mill)", filterType: FilterSubstrings}, + compileTest{filterStr: "(sn=*Mill*)", filterType: FilterSubstrings}, + compileTest{filterStr: "(sn>=Miller)", filterType: FilterGreaterOrEqual}, + compileTest{filterStr: "(sn<=Miller)", filterType: FilterLessOrEqual}, + compileTest{filterStr: "(sn=*)", filterType: FilterPresent}, + compileTest{filterStr: "(sn~=Miller)", filterType: FilterApproxMatch}, + // compileTest{ filterStr: "()", filterType: FilterExtensibleMatch }, +} + +func TestFilter(t *testing.T) { + // Test Compiler and Decompiler + for _, i := range testFilters { + filter, err := CompileFilter(i.filterStr) + if err != nil { + t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error()) + } else if filter.Tag != uint8(i.filterType) { + t.Errorf("%q Expected %q got %q", i.filterStr, FilterMap[uint64(i.filterType)], FilterMap[uint64(filter.Tag)]) + } else { + o, err := DecompileFilter(filter) + if err != nil { + t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error()) + } else if i.filterStr != o { + t.Errorf("%q expected, got %q", i.filterStr, o) + } + } + } +} + +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++ { + 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], _ = CompileFilter(i.filterStr) + } + + maxIdx := len(filters) + b.StartTimer() + for i := 0; i < b.N; i++ { + DecompileFilter(filters[i%maxIdx]) + } +} diff --git a/pkg/components/ldap/ldap.go b/pkg/components/ldap/ldap.go new file mode 100644 index 00000000000..e990b36231f --- /dev/null +++ b/pkg/components/ldap/ldap.go @@ -0,0 +1,302 @@ +// 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" + "fmt" + "io/ioutil" + + "github.com/gogits/gogs/modules/asn1-ber" +) + +// 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 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 +) + +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", +} + +// 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 := 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" + } + } +} + +func addRequestDescriptions(packet *ber.Packet) { + packet.Description = "LDAP Request" + packet.Children[0].Description = "Message ID" + packet.Children[1].Description = ApplicationMap[packet.Children[1].Tag] + if len(packet.Children) == 3 { + addControlDescriptions(packet.Children[2]) + } +} + +func addDefaultLDAPResponseDescriptions(packet *ber.Packet) { + resultCode := packet.Children[1].Children[0].Value.(uint64) + packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[uint8(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(file, "") + packet := ber.DecodePacket(file) + addLDAPDescriptions(packet) + ber.PrintPacket(packet) + + return nil +} + +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 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 { + return uint8(response.Children[0].Value.(uint64)), response.Children[2].Value.(string) + } + } + + return ErrorNetwork, "Invalid packet format" +} diff --git a/pkg/components/ldap/ldap_test.go b/pkg/components/ldap/ldap_test.go new file mode 100644 index 00000000000..31cfbf02f1b --- /dev/null +++ b/pkg/components/ldap/ldap_test.go @@ -0,0 +1,123 @@ +package ldap + +import ( + "fmt" + "testing" +) + +var ldapServer = "ldap.itd.umich.edu" +var ldapPort = uint16(389) +var baseDN = "dc=umich,dc=edu" +var filter = []string{ + "(cn=cis-fac)", + "(&(objectclass=rfc822mailgroup)(cn=*Computer*))", + "(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"} +var attributes = []string{ + "cn", + "description"} + +func TestConnect(t *testing.T) { + fmt.Printf("TestConnect: starting...\n") + l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + t.Errorf(err.Error()) + return + } + defer l.Close() + fmt.Printf("TestConnect: finished...\n") +} + +func TestSearch(t *testing.T) { + fmt.Printf("TestSearch: starting...\n") + l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + t.Errorf(err.Error()) + return + } + defer l.Close() + + searchRequest := NewSearchRequest( + baseDN, + ScopeWholeSubtree, 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 TestSearchWithPaging(t *testing.T) { + fmt.Printf("TestSearchWithPaging: starting...\n") + l, err := 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 := NewSearchRequest( + baseDN, + ScopeWholeSubtree, DerefAlways, 0, 0, false, + filter[1], + 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 testMultiGoroutineSearch(t *testing.T, l *Conn, results chan *SearchResult, i int) { + searchRequest := NewSearchRequest( + baseDN, + ScopeWholeSubtree, 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) { + fmt.Printf("TestMultiGoroutineSearch: starting...\n") + l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + t.Errorf(err.Error()) + return + } + defer l.Close() + + results := make([]chan *SearchResult, len(filter)) + for i := range filter { + results[i] = make(chan *SearchResult) + go testMultiGoroutineSearch(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)) + } + } +} diff --git a/pkg/components/ldap/modify.go b/pkg/components/ldap/modify.go new file mode 100644 index 00000000000..decc1eddca0 --- /dev/null +++ b/pkg/components/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" + + "github.com/gogits/gogs/modules/asn1-ber" +) + +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/pkg/components/ldap/search.go b/pkg/components/ldap/search.go new file mode 100644 index 00000000000..e2a62064468 --- /dev/null +++ b/pkg/components/ldap/search.go @@ -0,0 +1,350 @@ +// 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" + "strings" + + "github.com/gogits/gogs/modules/asn1-ber" +) + +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", +} + +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) GetAttributeValue(attribute string) string { + values := e.GetAttributeValues(attribute) + if len(values) == 0 { + return "" + } + 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) + } +} + +type EntryAttribute struct { + Name string + Values []string +} + +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)) + } + 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 +} From eb793f7feb38a10ce5e6c50a6e55d35b3afff2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Jun 2015 09:34:42 +0200 Subject: [PATCH 002/417] Initial work on ldap support, #1450 --- conf/defaults.ini | 12 ++++++ pkg/api/api.go | 2 +- pkg/api/ldap/ldap.go | 0 pkg/api/ldapauth/ldapauth.go | 56 +++++++++++++++++++++++++++ pkg/api/login.go | 48 +++++++++++++++++------- pkg/auth/auth.go | 73 ++++++++++++++++++++++++++++++++++++ pkg/setting/setting.go | 8 ++++ pkg/setting/setting_ldap.go | 19 ++++++++++ 8 files changed, 204 insertions(+), 14 deletions(-) delete mode 100644 pkg/api/ldap/ldap.go create mode 100644 pkg/api/ldapauth/ldapauth.go create mode 100644 pkg/auth/auth.go create mode 100644 pkg/setting/setting_ldap.go diff --git a/conf/defaults.ini b/conf/defaults.ini index 258a0198155..e3e5f6eb5f9 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -174,6 +174,18 @@ header_name = X-WEBAUTH-USER header_property = username auto_sign_up = true +#################################### Auth LDAP ########################## +[auth.ldap] +enabled = true +hosts = ldap://localhost.com:389 +use_ssl = false +base_dn = dc=grafana,dc=org +bind_path = cn=%username%,dc=grafana,dc=org +attr_username = cn +attr_name = cn +attr_surname = sn +attr_email = email + #################################### Logging ########################## [log] # Either "console", "file", default is "console" diff --git a/pkg/api/api.go b/pkg/api/api.go index 6ecaa51652e..0d8bceed4f8 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -19,7 +19,7 @@ func Register(r *macaron.Macaron) { // not logged in views r.Get("/", reqSignedIn, Index) r.Get("/logout", Logout) - r.Post("/login", bind(dtos.LoginCommand{}), LoginPost) + r.Post("/login", bind(dtos.LoginCommand{}), wrap(LoginPost)) r.Get("/login/:name", OAuthLogin) r.Get("/login", LoginView) diff --git a/pkg/api/ldap/ldap.go b/pkg/api/ldap/ldap.go deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/pkg/api/ldapauth/ldapauth.go b/pkg/api/ldapauth/ldapauth.go new file mode 100644 index 00000000000..ea6c0421e12 --- /dev/null +++ b/pkg/api/ldapauth/ldapauth.go @@ -0,0 +1,56 @@ +package ldapauth + +import ( + "errors" + "fmt" + "net/url" + + "github.com/gogits/gogs/modules/ldap" + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/setting" +) + +var ( + ErrInvalidCredentials = errors.New("Invalid Username or Password") +) + +func Login(username, password string) error { + url, err := url.Parse(setting.LdapUrls[0]) + if err != nil { + return err + } + + log.Info("Host: %v", url.Host) + conn, err := ldap.Dial("tcp", url.Host) + if err != nil { + return err + } + + defer conn.Close() + + bindFormat := "cn=%s,dc=grafana,dc=org" + + nx := fmt.Sprintf(bindFormat, username) + err = conn.Bind(nx, password) + + if err != nil { + if ldapErr, ok := err.(*ldap.Error); ok { + if ldapErr.ResultCode == 49 { + return ErrInvalidCredentials + } + } + return err + } + return nil + + // search := ldap.NewSearchRequest(url.Path, + // ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + // fmt.Sprintf(ls.Filter, name), + // []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail}, + // nil) + // sr, err := l.Search(search) + // if err != nil { + // log.Debug("LDAP Authen OK but not in filter %s", name) + // return "", "", "", "", false + // } +} diff --git a/pkg/api/login.go b/pkg/api/login.go index 0fc5651d5f9..f6a511bffbe 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -4,6 +4,8 @@ import ( "net/url" "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/api/ldapauth" + "github.com/grafana/grafana/pkg/auth" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/metrics" @@ -86,21 +88,28 @@ func LoginApiPing(c *middleware.Context) { c.JsonOK("Logged in") } -func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) { - userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.User} - err := bus.Dispatch(&userQuery) - - if err != nil { - c.JsonApiErr(401, "Invalid username or password", err) - return +func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response { + sourcesQuery := auth.GetAuthSourcesQuery{} + if err := bus.Dispatch(&sourcesQuery); err != nil { + return ApiError(500, "Could not get login sources", err) } - user := userQuery.Result + var err error + var user *m.User - passwordHashed := util.EncodePassword(cmd.Password, user.Salt) - if passwordHashed != user.Password { - c.JsonApiErr(401, "Invalid username or password", err) - return + for _, authSource := range sourcesQuery.Sources { + user, err = authSource.AuthenticateUser(cmd.User, cmd.Password) + if err == nil { + break + } + // handle non invalid credentials error, otherwise try next auth source + if err != auth.ErrInvalidCredentials { + return ApiError(500, "Error while trying to authenticate user", err) + } + } + + if err != nil { + return ApiError(401, "Invalid username or password", err) } loginUserWithUser(user, c) @@ -116,7 +125,20 @@ func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) { metrics.M_Api_Login_Post.Inc(1) - c.JSON(200, result) + return Json(200, result) +} + +func LoginUsingLdap(c *middleware.Context, cmd dtos.LoginCommand) Response { + err := ldapauth.Login(cmd.User, cmd.Password) + + if err != nil { + if err == ldapauth.ErrInvalidCredentials { + return ApiError(401, "Invalid username or password", err) + } + return ApiError(500, "Ldap login failed", err) + } + + return Empty(401) } func loginUserWithUser(user *m.User, c *middleware.Context) { diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go new file mode 100644 index 00000000000..a236663ed44 --- /dev/null +++ b/pkg/auth/auth.go @@ -0,0 +1,73 @@ +package auth + +import ( + "errors" + + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/util" +) + +var ( + ErrInvalidCredentials = errors.New("Invalid Username or Password") +) + +type LoginSettings struct { + LdapEnabled bool +} + +type LdapFilterToOrg struct { + Filter string + OrgId int + OrgRole string +} + +type LdapSettings struct { + Enabled bool + Hosts []string + UseSSL bool + BindDN string + AttrUsername string + AttrName string + AttrSurname string + AttrMail string + Filters []LdapFilterToOrg +} + +type AuthSource interface { + AuthenticateUser(username, password string) (*m.User, error) +} + +type GetAuthSourcesQuery struct { + Sources []AuthSource +} + +func init() { + bus.AddHandler("auth", GetAuthSources) +} + +func GetAuthSources(query *GetAuthSourcesQuery) error { + query.Sources = []AuthSource{&GrafanaDBAuthSource{}} + return nil +} + +type GrafanaDBAuthSource struct { +} + +func (s *GrafanaDBAuthSource) AuthenticateUser(username, password string) (*m.User, error) { + userQuery := m.GetUserByLoginQuery{LoginOrEmail: username} + err := bus.Dispatch(&userQuery) + + if err != nil { + return nil, ErrInvalidCredentials + } + + user := userQuery.Result + + passwordHashed := util.EncodePassword(password, user.Salt) + if passwordHashed != user.Password { + return nil, ErrInvalidCredentials + } + + return user, nil +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 6768f9aabd9..3cb4792ca82 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -114,6 +114,10 @@ var ( ReportingEnabled bool GoogleAnalyticsId string + + // LDAP + LdapEnabled bool + LdapUrls []string ) type CommandLineArgs struct { @@ -406,6 +410,10 @@ func NewConfigContext(args *CommandLineArgs) { ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true) GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String() + ldapSec := Cfg.Section("auth.ldap") + LdapEnabled = ldapSec.Key("enabled").MustBool(false) + LdapUrls = ldapSec.Key("urls").Strings(" ") + readSessionConfig() } diff --git a/pkg/setting/setting_ldap.go b/pkg/setting/setting_ldap.go new file mode 100644 index 00000000000..26592159f1f --- /dev/null +++ b/pkg/setting/setting_ldap.go @@ -0,0 +1,19 @@ +package setting + +type LdapFilterToOrg struct { + Filter string + OrgId int + OrgRole string +} + +type LdapSettings struct { + Enabled bool + Hosts []string + UseSSL bool + BindDN string + AttrUsername string + AttrName string + AttrSurname string + AttrMail string + Filters []LdapFilterToOrg +} From 2dbb370955078089db6cd75b5e2c233cfc23ce97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 26 Jun 2015 18:45:23 +0200 Subject: [PATCH 003/417] Working on resize handle, drag to resize panels & rows --- public/app/features/dashboard/rowCtrl.js | 4 +- public/app/features/panel/panelDirective.js | 62 ++++++++++++++++++- public/app/features/panel/partials/panel.html | 1 + public/css/less/panel.less | 10 +++ .../angular-native-dragdrop/draganddrop.js | 4 ++ 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard/rowCtrl.js b/public/app/features/dashboard/rowCtrl.js index c63017365bb..58005dc827d 100644 --- a/public/app/features/dashboard/rowCtrl.js +++ b/public/app/features/dashboard/rowCtrl.js @@ -98,7 +98,7 @@ function (angular, app, _, config) { }; $scope.updatePanelSpan = function(panel, span) { - panel.span = Math.min(Math.max(panel.span + span, 1), 12); + panel.span = Math.min(Math.max(Math.floor(panel.span + span), 1), 12); }; $scope.replacePanel = function(newPanel, oldPanel) { @@ -121,7 +121,7 @@ function (angular, app, _, config) { module.directive('rowHeight', function() { return function(scope, element) { scope.$watchGroup(['row.collapse', 'row.height'], function() { - element[0].style.minHeight = scope.row.collapse ? '5px' : scope.row.height; + element.css({ minHeight: scope.row.collapse ? '5px' : scope.row.height }); }); }; }); diff --git a/public/app/features/panel/panelDirective.js b/public/app/features/panel/panelDirective.js index 7330bb627de..189ce5d731d 100644 --- a/public/app/features/panel/panelDirective.js +++ b/public/app/features/panel/panelDirective.js @@ -22,7 +22,8 @@ function (angular, $, config) { }); } }; - }).directive('grafanaPanel', function() { + }) + .directive('grafanaPanel', function() { return { restrict: 'E', templateUrl: 'app/features/panel/partials/panel.html', @@ -36,5 +37,64 @@ function (angular, $, config) { }); } }; + }) + .directive('panelResizer', function($rootScope) { + return { + restrict: 'E', + template: '', + link: function(scope, elem) { + var resizing = false; + var handleOffset; + var originalHeight; + var originalWidth; + var maxWidth; + + function dragStartHandler(e) { + e.preventDefault(); + console.log('start'); + resizing = true; + + handleOffset = $(e.target).offset(); + originalHeight = parseInt(scope.row.height); + originalWidth = scope.panel.span; + maxWidth = $(document).width(); + + $('body').on('mousemove', moveHandler); + $('body').on('mouseup', dragEndHandler); + } + + function moveHandler(e) { + scope.row.height = originalHeight + (e.pageY - handleOffset.top); + scope.panel.span = originalWidth + (((e.pageX - handleOffset.left) / maxWidth) * 12); + + var rowSpan = scope.dashboard.rowSpan(scope.row); + + if (Math.floor(rowSpan) < 14) { + scope.row.panels[scope.row.panels.length - 1].span = scope.row.panels[scope.row.panels.length - 1].span - (rowSpan - 12); + } + + scope.$apply(function() { + scope.$broadcast('render'); + }); + } + + function dragEndHandler() { + console.log('end'); + scope.$apply(function() { + $rootScope.$broadcast('render'); + }); + + $('body').off('mousemove', moveHandler); + $('body').off('mouseup', dragEndHandler); + } + + elem.on('mousedown', dragStartHandler); + + scope.$on("$destroy", function() { + elem.off('mousedown', dragStartHandler); + }); + } + }; }); + }); diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index 6fbab9de70a..a7fef0f5d67 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -16,6 +16,7 @@
+
diff --git a/public/css/less/panel.less b/public/css/less/panel.less index 6f011b2978f..8292ceb1267 100644 --- a/public/css/less/panel.less +++ b/public/css/less/panel.less @@ -203,3 +203,13 @@ top: 0; right: 0; } + +.resize-panel-handle { + cursor: se-resize; + position: absolute; + bottom: 0; + right: 0; + width: 15px; + height: 15px; + display: block; +} diff --git a/public/vendor/angular-native-dragdrop/draganddrop.js b/public/vendor/angular-native-dragdrop/draganddrop.js index f45b47df4d9..2616241607b 100644 --- a/public/vendor/angular-native-dragdrop/draganddrop.js +++ b/public/vendor/angular-native-dragdrop/draganddrop.js @@ -75,6 +75,10 @@ function dragstartHandler(e) { var isDragAllowed = !isDragHandleUsed || dragTarget.classList.contains(dragHandleClass); + if (dragTarget.classList.contains("resize-panel-handle")) { + return; + } + if (isDragAllowed) { var sendChannel = attrs.dragChannel || 'defaultchannel'; var dragData = ''; From 521072daea675a90518d9a913f69799437e6694a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Fjellv=C3=A6r=20Olsen?= Date: Tue, 7 Jul 2015 19:57:36 +0200 Subject: [PATCH 004/417] KairosDB: Streamline the Templating with the very related OpenTSDB plugin Since KairosDB is a fork of OpenTSDB it makes sense to (At least for the time being) keep their code bases similar-ish. --- .../plugins/datasource/kairosdb/datasource.js | 102 ++++++++++-------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/public/app/plugins/datasource/kairosdb/datasource.js b/public/app/plugins/datasource/kairosdb/datasource.js index d0cddca3bec..a47b6a9e83f 100644 --- a/public/app/plugins/datasource/kairosdb/datasource.js +++ b/public/app/plugins/datasource/kairosdb/datasource.js @@ -76,7 +76,7 @@ function (angular, _, kbn) { * Gets the list of metrics * @returns {*|Promise} */ - KairosDBDatasource.prototype.performMetricSuggestQuery = function() { + KairosDBDatasource.prototype._performMetricSuggestQuery = function(metric) { var options = { url : this.url + '/api/v1/metricnames', method : 'GET' @@ -84,46 +84,74 @@ function (angular, _, kbn) { return $http(options).then(function(response) { if (!response.data) { - return []; + return $q.when([]); } - return response.data.results; + var metrics = []; + _.each(response.data.results, function(r) { + if (r.indexOf(metric) >= 0) { + metrics.push(r); + } + }); + return metrics; }); }; - KairosDBDatasource.prototype.performListTagNames = function() { + KairosDBDatasource.prototype._performMetricKeyLookup = function(metric) { + if(!metric) { return $q.when([]); } + var options = { - url : this.url + '/api/v1/tagnames', - method : 'GET' + method: 'POST', + url: this.url + '/api/v1/datapoints/query/tags', + data: { + metrics : [{ name : metric }], + cache_time : 0, + start_absolute: 0 + } }; - return $http(options).then(function(response) { - if (!response.data) { - return []; + return $http(options).then(function(result) { + if (!result.data) { + return $q.when([]); } - return response.data.results; + var tagks = []; + _.each(result.data.queries[0].results[0].tags, function(tagv, tagk) { + if(tagks.indexOf(tagk) === -1) { + tagks.push(tagk); + } + }); + return tagks; }); }; - KairosDBDatasource.prototype.performListTagValues = function() { + KairosDBDatasource.prototype._performMetricKeyValueLookup = function(metric, key) { + if(!metric || !key) { + return $q.when([]); + } + var options = { - url : this.url + '/api/v1/tagvalues', - method : 'GET' + method: 'POST', + url: this.url + '/api/v1/datapoints/query/tags', + data: { + metrics : [{ name : metric }], + cache_time : 0, + start_absolute: 0 + } }; - return $http(options).then(function(response) { - if (!response.data) { - return []; + return $http(options).then(function(result) { + if (!result.data) { + return $q.when([]); } - return response.data.results; + return result.data.queries[0].results[0].tags[key]; }); }; - KairosDBDatasource.prototype.performTagSuggestQuery = function(metricname) { + KairosDBDatasource.prototype.performTagSuggestQuery = function(metric) { var options = { url : this.url + '/api/v1/datapoints/query/tags', method : 'POST', data : { - metrics : [{ name : metricname }], + metrics : [{ name : metric }], cache_time : 0, start_absolute: 0 } @@ -140,19 +168,7 @@ function (angular, _, kbn) { }; KairosDBDatasource.prototype.metricFindQuery = function(query) { - function format(results, query) { - return _.chain(results) - .filter(function(result) { - return result.indexOf(query) >= 0; - }) - .map(function(result) { - return { - text: result, - expandable: true - }; - }) - .value(); - } + if (!query) { return $q.when([]); } var interpolated; try { @@ -162,30 +178,32 @@ function (angular, _, kbn) { return $q.reject(err); } + var responseTransform = function(result) { + return _.map(result, function(value) { + return {text: value}; + }); + }; + var metrics_regex = /metrics\((.*)\)/; var tag_names_regex = /tag_names\((.*)\)/; - var tag_values_regex = /tag_values\((.*)\)/; + var tag_values_regex = /tag_values\((.*),\s?(.*?)\)/; var metrics_query = interpolated.match(metrics_regex); if (metrics_query) { - return this.performMetricSuggestQuery().then(function(metrics) { - return format(metrics, metrics_query[1]); - }); + return this._performMetricSuggestQuery(metrics_query[1]).then(responseTransform); } var tag_names_query = interpolated.match(tag_names_regex); if (tag_names_query) { - return this.performListTagNames().then(function(tag_names) { - return format(tag_names, tag_names_query[1]); - }); + return this._performMetricKeyLookup(tag_names_query[1]).then(responseTransform); } var tag_values_query = interpolated.match(tag_values_regex); if (tag_values_query) { - return this.performListTagValues().then(function(tag_values) { - return format(tag_values, tag_values_query[1]); - }); + return this._performMetricKeyValueLookup(tag_values_query[1], tag_values_query[2]).then(responseTransform); } + + return $q.when([]); }; ///////////////////////////////////////////////////////////////////////// From 2255cb53f0867087c994c7dd20a5b83adfcee72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Fjellv=C3=A6r=20Olsen?= Date: Wed, 8 Jul 2015 09:18:02 +0200 Subject: [PATCH 005/417] Close the gap between the key and the value in the js objects --- .../plugins/datasource/kairosdb/datasource.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/public/app/plugins/datasource/kairosdb/datasource.js b/public/app/plugins/datasource/kairosdb/datasource.js index a47b6a9e83f..ad2dd0f52de 100644 --- a/public/app/plugins/datasource/kairosdb/datasource.js +++ b/public/app/plugins/datasource/kairosdb/datasource.js @@ -78,8 +78,8 @@ function (angular, _, kbn) { */ KairosDBDatasource.prototype._performMetricSuggestQuery = function(metric) { var options = { - url : this.url + '/api/v1/metricnames', - method : 'GET' + url: this.url + '/api/v1/metricnames', + method: 'GET' }; return $http(options).then(function(response) { @@ -103,8 +103,8 @@ function (angular, _, kbn) { method: 'POST', url: this.url + '/api/v1/datapoints/query/tags', data: { - metrics : [{ name : metric }], - cache_time : 0, + metrics: [{ name: metric }], + cache_time: 0, start_absolute: 0 } }; @@ -132,8 +132,8 @@ function (angular, _, kbn) { method: 'POST', url: this.url + '/api/v1/datapoints/query/tags', data: { - metrics : [{ name : metric }], - cache_time : 0, + metrics: [{ name: metric }], + cache_time: 0, start_absolute: 0 } }; @@ -148,11 +148,11 @@ function (angular, _, kbn) { KairosDBDatasource.prototype.performTagSuggestQuery = function(metric) { var options = { - url : this.url + '/api/v1/datapoints/query/tags', - method : 'POST', - data : { - metrics : [{ name : metric }], - cache_time : 0, + url: this.url + '/api/v1/datapoints/query/tags', + method: 'POST', + data: { + metrics: [{ name: metric }], + cache_time: 0, start_absolute: 0 } }; From 3980d25c23e2fd59b3c5eb6ed7a0b32e190e71a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Fjellv=C3=A6r=20Olsen?= Date: Wed, 8 Jul 2015 09:18:47 +0200 Subject: [PATCH 006/417] Remove superfluous whitespaces --- .../plugins/datasource/kairosdb/datasource.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/public/app/plugins/datasource/kairosdb/datasource.js b/public/app/plugins/datasource/kairosdb/datasource.js index ad2dd0f52de..a718d4f0e44 100644 --- a/public/app/plugins/datasource/kairosdb/datasource.js +++ b/public/app/plugins/datasource/kairosdb/datasource.js @@ -100,13 +100,13 @@ function (angular, _, kbn) { if(!metric) { return $q.when([]); } var options = { - method: 'POST', - url: this.url + '/api/v1/datapoints/query/tags', - data: { + method: 'POST', + url: this.url + '/api/v1/datapoints/query/tags', + data: { metrics: [{ name: metric }], cache_time: 0, start_absolute: 0 - } + } }; return $http(options).then(function(result) { @@ -129,13 +129,13 @@ function (angular, _, kbn) { } var options = { - method: 'POST', - url: this.url + '/api/v1/datapoints/query/tags', - data: { + method: 'POST', + url: this.url + '/api/v1/datapoints/query/tags', + data: { metrics: [{ name: metric }], cache_time: 0, start_absolute: 0 - } + } }; return $http(options).then(function(result) { From 3e05eb23fd489d5d4b60692e948544144733d922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Fjellv=C3=A6r=20Olsen?= Date: Wed, 8 Jul 2015 09:24:05 +0200 Subject: [PATCH 007/417] Update documentation based on templating changes --- docs/sources/datasources/kairosdb.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/sources/datasources/kairosdb.md b/docs/sources/datasources/kairosdb.md index f0d52b91548..f7ead4897ab 100644 --- a/docs/sources/datasources/kairosdb.md +++ b/docs/sources/datasources/kairosdb.md @@ -36,12 +36,13 @@ KairosDB Datasource Plugin provides following functions in `Variables values que Name | Description ---- | ---- -`metrics(query)` | Returns a list of metric names. If nothing is given, returns a list of all metric names. -`tag_names(query)` | Returns a list of tag names. If nothing is given, returns a list of all tag names. -`tag_values(query)` | Returns a list of tag values. If nothing is given, returns a list of all tag values. +`metrics(query)` | Returns a list of metric names matching `query`. If nothing is given, returns a list of all metric names. +`tag_names(query)` | Returns a list of tag names matching `query`. If nothing is given, returns a list of all tag names. +`tag_values(metric, tag)` | Returns a list of values for `tag` from the given `metric`. For details of `metric names`, `tag names`, and `tag values`, please refer to the KairosDB documentations. - [List Metric Names - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListMetricNames.html) - [List Tag Names - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListTagNames.html) - [List Tag Values - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListTagValues.html) +- [Query Metrics - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/QueryMetrics.html). From 48975e6533e2427603a47ae4119fc5c96bb45cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Fjellv=C3=A6r=20Olsen?= Date: Wed, 8 Jul 2015 09:36:25 +0200 Subject: [PATCH 008/417] Keep QueryController up to date as well --- public/app/plugins/datasource/kairosdb/queryCtrl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/kairosdb/queryCtrl.js b/public/app/plugins/datasource/kairosdb/queryCtrl.js index 9e8c5817dd1..a647b3f84a3 100644 --- a/public/app/plugins/datasource/kairosdb/queryCtrl.js +++ b/public/app/plugins/datasource/kairosdb/queryCtrl.js @@ -53,7 +53,7 @@ function (angular, _) { return metricList; } else { - $scope.datasource.performMetricSuggestQuery().then(function(result) { + $scope.datasource._performMetricSuggestQuery().then(function(result) { metricList = result; callback(metricList); }); @@ -69,7 +69,7 @@ function (angular, _) { } } - $scope.datasource.performTagSuggestQuery($scope.target.metric).then(function(result) { + $scope.datasource._performTagSuggestQuery($scope.target.metric).then(function(result) { if (!_.isEmpty(result)) { tagList.push(result); callback(_.keys(result.tags)); From d97f24cfc3775006162db8c8af967cad8275ee49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20Fjellv=C3=A6r=20Olsen?= Date: Wed, 8 Jul 2015 13:00:22 +0200 Subject: [PATCH 009/417] Update QueryController to conform with OpenTSDB Controller --- .../plugins/datasource/kairosdb/queryCtrl.js | 52 +++++-------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/public/app/plugins/datasource/kairosdb/queryCtrl.js b/public/app/plugins/datasource/kairosdb/queryCtrl.js index a647b3f84a3..4cf1b8c0f21 100644 --- a/public/app/plugins/datasource/kairosdb/queryCtrl.js +++ b/public/app/plugins/datasource/kairosdb/queryCtrl.js @@ -6,8 +6,6 @@ function (angular, _) { 'use strict'; var module = angular.module('grafana.controllers'); - var metricList = []; - var tagList = []; module.controller('KairosDBQueryCtrl', function($scope) { @@ -48,50 +46,26 @@ function (angular, _) { _.move($scope.panel.targets, fromIndex, toIndex); }; + $scope.getTextValues = function(metricFindResult) { + return _.map(metricFindResult, function(value) { return value.text; }); + }; + $scope.suggestMetrics = function(query, callback) { - if (!_.isEmpty(metricList)) { - return metricList; - } - else { - $scope.datasource._performMetricSuggestQuery().then(function(result) { - metricList = result; - callback(metricList); - }); - } + $scope.datasource.metricFindQuery('metrics(' + query + ')') + .then($scope.getTextValues) + .then(callback); }; $scope.suggestTagKeys = function(query, callback) { - if (!_.isEmpty(tagList)) { - var result = _.find(tagList, { name : $scope.target.metric }); - - if (!_.isEmpty(result)) { - return _.keys(result.tags); - } - } - - $scope.datasource._performTagSuggestQuery($scope.target.metric).then(function(result) { - if (!_.isEmpty(result)) { - tagList.push(result); - callback(_.keys(result.tags)); - } - }); + $scope.datasource.metricFindQuery('tag_names(' + $scope.target.metric + ')') + .then($scope.getTextValues) + .then(callback); }; $scope.suggestTagValues = function(query, callback) { - if (!_.isEmpty(tagList)) { - var result = _.find(tagList, { name : $scope.target.metric }); - - if (!_.isEmpty(result)) { - return result.tags[$scope.target.currentTagKey]; - } - } - - $scope.datasource.performTagSuggestQuery($scope.target.metric).then(function(result) { - if (!_.isEmpty(result)) { - tagList.push(result); - callback(result.tags[$scope.target.currentTagKey]); - } - }); + $scope.datasource.metricFindQuery('tag_values(' + $scope.target.metric + ',' + $scope.target.currentTagKey + ')') + .then($scope.getTextValues) + .then(callback); }; // Filter metric by tag From db1847bc1d5ae3aa91903adb0e0cbeec3118ea67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 10 Jul 2015 15:29:34 +0200 Subject: [PATCH 010/417] More work on ldap, gotten ldap search (read attributes) to work --- conf/defaults.ini | 3 +-- pkg/auth/ldap.go | 35 ++++++++++++++++++++++++++++------- pkg/setting/setting.go | 6 ++++-- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index a44d47e2daf..e9b158d11bb 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -184,8 +184,7 @@ auto_sign_up = true enabled = true hosts = ldap://127.0.0.1:389 use_ssl = false -base_dn = dc=grafana,dc=org -bind_path = cn=%username%,dc=grafana,dc=org +bind_path = cn=%s,dc=grafana,dc=org attr_username = cn attr_name = cn attr_surname = sn diff --git a/pkg/auth/ldap.go b/pkg/auth/ldap.go index 4a9b524cdbb..97e89f1453d 100644 --- a/pkg/auth/ldap.go +++ b/pkg/auth/ldap.go @@ -17,7 +17,6 @@ func loginUsingLdap(query *AuthenticateUserQuery) error { return err } - log.Info("Host: %v", url.Host) conn, err := ldap.Dial("tcp", url.Host) if err != nil { return err @@ -25,10 +24,8 @@ func loginUsingLdap(query *AuthenticateUserQuery) error { defer conn.Close() - bindFormat := "cn=%s,dc=grafana,dc=org" - - nx := fmt.Sprintf(bindFormat, query.Username) - err = conn.Bind(nx, query.Password) + bindPath := fmt.Sprintf(setting.LdapBindPath, query.Username) + err = conn.Bind(bindPath, query.Password) if err != nil { if ldapErr, ok := err.(*ldap.Error); ok { @@ -39,12 +36,31 @@ func loginUsingLdap(query *AuthenticateUserQuery) error { return err } - userQuery := m.GetUserByLoginQuery{LoginOrEmail: "admin"} + searchReq := ldap.SearchRequest{ + BaseDN: "dc=grafana,dc=org", + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldap.NeverDerefAliases, + Attributes: []string{"cn", "sn", "email"}, + Filter: fmt.Sprintf("(cn=%s)", query.Username), + } + + result, err := conn.Search(&searchReq) + if err != nil { + return err + } + + log.Info("Search result: %v, error: %v", result, err) + + for _, entry := range result.Entries { + log.Info("cn: %s", entry.Attributes[0].Values[0]) + log.Info("email: %s", entry.Attributes[2].Values[0]) + } + + userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username} err = bus.Dispatch(&userQuery) if err != nil { if err == m.ErrUserNotFound { - return ErrInvalidCredentials } return err } @@ -53,3 +69,8 @@ func loginUsingLdap(query *AuthenticateUserQuery) error { return nil } + +func createUserFromLdapInfo() error { + return nil + +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 6dd583a586b..c6c5630d2f8 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -118,8 +118,9 @@ var ( GoogleAnalyticsId string // LDAP - LdapEnabled bool - LdapHosts []string + LdapEnabled bool + LdapHosts []string + LdapBindPath string // SMTP email settings Smtp SmtpSettings @@ -419,6 +420,7 @@ func NewConfigContext(args *CommandLineArgs) { ldapSec := Cfg.Section("auth.ldap") LdapEnabled = ldapSec.Key("enabled").MustBool(false) LdapHosts = ldapSec.Key("hosts").Strings(" ") + LdapBindPath = ldapSec.Key("bind_path").String() readSessionConfig() readSmtpSettings() From 9f6c9cd6ffbc03475f4908313f91fa63063beca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 12 Jul 2015 18:18:04 +0200 Subject: [PATCH 011/417] fix(graphite): raw query mode (disable graphite query editor mode) is now persisted property on the query, Fixes #2328, Fixes #2307 --- .../graphite/partials/query.editor.html | 13 ++++++------- .../app/plugins/datasource/graphite/queryCtrl.js | 15 +++++++++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/public/app/plugins/datasource/graphite/partials/query.editor.html b/public/app/plugins/datasource/graphite/partials/query.editor.html index dcbbdc69a8a..add85c26773 100755 --- a/public/app/plugins/datasource/graphite/partials/query.editor.html +++ b/public/app/plugins/datasource/graphite/partials/query.editor.html @@ -12,7 +12,7 @@
  • - +
  • @@ -65,15 +65,14 @@ - + ng-model-onblur ng-change="get_data()" + ng-show="target.textEditor" /> -
    - +