Started work on LDAP again, #1450

This commit is contained in:
Torkel Ödegaard 2015-07-10 11:10:48 +02:00
commit 0ef7271326
376 changed files with 32968 additions and 8624 deletions

3
.bowerrc Normal file
View File

@ -0,0 +1,3 @@
{
"directory": "public/vendor/"
}

View File

@ -1,18 +1,27 @@
# 2.1.0 (unreleased - master branch)
**Data sources**
- [Issue #1525](https://github.com/grafana/grafana/issues/1525). InfluxDB: Full support for InfluxDB 0.9 with new adapted query editor
- [Issue #2191](https://github.com/grafana/grafana/issues/2191). KariosDB: Grafana now ships with a KariosDB data source plugin, thx @masaori335
- [Issue #1177](https://github.com/grafana/grafana/issues/1177). OpenTSDB: Limit tags by metric, OpenTSDB config option tsd.core.meta.enable_realtime_ts must enabled for OpenTSDB lookup api
- [Issue #1250](https://github.com/grafana/grafana/issues/1250). OpenTSDB: Support for template variable values lookup queries
**New dashboard features**
- [Issue #1144](https://github.com/grafana/grafana/issues/1144). Templating: You can now select multiple template variables values at the same time.
- [Issue #1922](https://github.com/grafana/grafana/issues/1922). Templating: Specify multiple variable values via URL params.
- [Issue #1888](https://github.com/grafana/grafana/issues/1144). Templating: Repeat panel or row for each selected template variable value
- [Issue #1888](https://github.com/grafana/grafana/issues/1944). Dashboard: Custom Navigation links & dynamic links to related dashboards
- [Issue #590](https://github.com/grafana/grafana/issues/590). Graph: Define series color using regex rule
- [Issue #2162](https://github.com/grafana/grafana/issues/2162). Graph: New series style override, negative-y transform and stack groups
- [Issue #2096](https://github.com/grafana/grafana/issues/2096). Dashboard list panel: Now supports search by multiple tags
- [Issue #2203](https://github.com/grafana/grafana/issues/2203). Singlestat: Now support string values
**User or Organization admin**
- [Issue #1899](https://github.com/grafana/grafana/issues/1899). Organization: You can now update the organization user role directly (without removing and readding the organization user).
- [Issue #2088](https://github.com/grafana/grafana/issues/2088). Roles: New user role `Read Only Editor` that replaces the old `Viewer` role behavior
**Backend**
- [Issue #2218](https://github.com/grafana/grafana/issues/2218). Auth: You can now authenicate against api with username / password using basic auth
- [Issue #2095](https://github.com/grafana/grafana/issues/2095). Search: Search now supports filtering by multiple dashboard tags
- [Issue #1905](https://github.com/grafana/grafana/issues/1905). Github OAuth: You can now configure a Github team membership requirement, thx @dewski
- [Issue #2052](https://github.com/grafana/grafana/issues/2052). Github OAuth: You can now configure a Github organization requirement, thx @indrekj
@ -27,6 +36,10 @@
- Search HTTP API response has changed (simplified), tags list moved to seperate HTTP resource URI
- Datasource HTTP api breaking change, ADD datasource is now POST /api/datasources/, update is now PUT /api/datasources/:id
**Fixes**
- [Issue #2185](https://github.com/grafana/grafana/issues/2185). Graph: fixed PNG rendering of panels with legend table to the right
- [Issue #2163](https://github.com/grafana/grafana/issues/2163). Backend: Load dashboards with capital letters in the dashboard url slug (url id)
# 2.0.3 (unreleased - 2.0.x branch)
**Fixes**

22
Godeps/Godeps.json generated
View File

@ -1,6 +1,6 @@
{
"ImportPath": "github.com/grafana/grafana",
"GoVersion": "go1.3",
"GoVersion": "go1.4.2",
"Packages": [
"./pkg/..."
],
@ -13,14 +13,6 @@
"ImportPath": "github.com/Unknwon/macaron",
"Rev": "93de4f3fad97bf246b838f828e2348f46f21f20a"
},
{
"ImportPath": "github.com/dalu/slug",
"Rev": "6dbd13912e9be466e2c1de349a2c7d1466c97e07"
},
{
"ImportPath": "github.com/dalu/unidecode",
"Rev": "339814d47f3e32a6f7036a0a4c56ed9b373dd755"
},
{
"ImportPath": "github.com/go-sql-driver/mysql",
"Comment": "v1.2-26-g9543750",
@ -35,6 +27,10 @@
"Comment": "v0.4.2-58-ge2889e5",
"Rev": "e2889e5517600b82905f1d2ba8b70deb71823ffe"
},
{
"ImportPath": "github.com/gosimple/slug",
"Rev": "8d258463b4459f161f51d6a357edacd3eef9d663"
},
{
"ImportPath": "github.com/jtolds/gls",
"Rev": "f1ac7f4f24f50328e6bc838ca4437d1612a0243c"
@ -56,6 +52,10 @@
"ImportPath": "github.com/mattn/go-sqlite3",
"Rev": "e28cd440fabdd39b9520344bc26829f61db40ece"
},
{
"ImportPath": "github.com/rainycape/unidecode",
"Rev": "836ef0a715aedf08a12d595ed73ec8ed5b288cac"
},
{
"ImportPath": "github.com/smartystreets/goconvey/convey",
"Comment": "1.5.0-356-gfbc0a1c",
@ -87,10 +87,6 @@
"ImportPath": "gopkg.in/redis.v2",
"Comment": "v2.3.2",
"Rev": "e6179049628164864e6e84e973cfb56335748dea"
},
{
"ImportPath": "gopkgs.com/pool.v1",
"Rev": "c850f092aad1780cbffff25f471c5cc32097932a"
}
]
}

View File

@ -1,6 +0,0 @@
unidecode
=========
Unicode transliterator in Golang - Replaces non-ASCII characters with their ASCII approximations.
View other available versions, documentation and examples at http://gopkgs.com/unidecode

View File

@ -0,0 +1,2 @@
_*
cover*.out

View File

@ -4,9 +4,10 @@ slug
Package `slug` generate slug from unicode string, URL-friendly slugify with
multiple languages support.
[![GoDoc](https://godoc.org/github.com/dalu/slug?status.png)](https://godoc.org/github.com/dalu/slug)
[![GoDoc](https://godoc.org/github.com/gosimple/slug?status.png)](https://godoc.org/github.com/gosimple/slug)
[![Build Status](https://drone.io/github.com/gosimple/slug/status.png)](https://drone.io/github.com/gosimple/slug/latest)
[Documentation online](http://godoc.org/github.com/dalu/slug)
[Documentation online](http://godoc.org/github.com/gosimple/slug)
## Example
@ -37,9 +38,12 @@ multiple languages support.
fmt.Println(textSub) // Will print 'sand-is-hot'
}
### Requests or bugs?
<https://github.com/gosimple/slug/issues>
## Installation
go get -u github.com/dalu/slug
go get -u github.com/gosimple/slug
## License

View File

@ -12,7 +12,7 @@ Example:
package main
import(
"github.com/dalu/slug"
"github.com/gosimple/slug"
"fmt"
)
@ -35,5 +35,9 @@ Example:
textSub := slug.Make("water is hot")
fmt.Println(textSub) // Will print 'sand-is-hot'
}
Requests or bugs?
https://github.com/gosimple/slug/issues
*/
package slug

View File

@ -6,9 +6,10 @@
package slug
import (
"github.com/dalu/unidecode"
"regexp"
"strings"
"github.com/rainycape/unidecode"
)
var (

View File

@ -0,0 +1,6 @@
unidecode
=========
Unicode transliterator in Golang - Replaces non-ASCII characters with their ASCII approximations.
[![GoDoc](https://godoc.org/github.com/rainycape/unidecode?status.svg)](https://godoc.org/github.com/rainycape/unidecode)

View File

@ -5,12 +5,9 @@ import (
"encoding/binary"
"io"
"strings"
"sync"
)
var (
decoded = false
mutex sync.Mutex
transliterations [65536][]rune
transCount = rune(len(transliterations))
getUint16 = binary.LittleEndian.Uint16

View File

@ -4,15 +4,15 @@
package unidecode
import (
"sync"
"unicode"
"gopkgs.com/pool.v1"
)
const pooledCapacity = 64
var (
slicePool = pool.New(0)
slicePool sync.Pool
decodingOnce sync.Once
)
// Unidecode implements a unicode transliterator, which
@ -23,14 +23,7 @@ var (
// with their closest ASCII counterparts.
// e.g. Unicode("áéíóú") => "aeiou"
func Unidecode(s string) string {
if !decoded {
mutex.Lock()
if !decoded {
decodeTransliterations()
decoded = true
}
mutex.Unlock()
}
decodingOnce.Do(decodeTransliterations)
l := len(s)
var r []rune
if l > pooledCapacity {

View File

@ -1,23 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,13 +0,0 @@
pool
====
sync.Pool compatibility layer for for Go - falls back to a channel based pool in Go &lt; 1.3
Please, use the following import path to ensure a stable API:
```go
import "gopkgs.com/pool.v1"
```
View other available versions, documentation and examples at http://gopkgs.com/pool

View File

@ -1,3 +0,0 @@
// Package pool provides a sync.Pool compatibility layer, which
// falls back to a channel based pool on Go < 1.3.
package pool

View File

@ -1,23 +0,0 @@
package pool_test
import (
"fmt"
"gopkgs.com/pool.v1"
)
func ExamplePool() {
p := pool.New(0)
p.Put("Hello")
fmt.Println(p.Get())
// OutPut: Hello
}
func ExamplePoolNew() {
p := pool.New(0)
p.New = func() interface{} {
return "World!"
}
fmt.Println(p.Get())
// OutPut: World!
}

View File

@ -1,24 +0,0 @@
package pool
import (
"fmt"
"reflect"
)
// gopkgs.go: v1
// NOTE: This file is autogenerated by gopkgs.com.
const (
goPkgsSrcPath = "github.com/rainycape/pool"
goPkgsName = "pool"
goPkgsErrFmt = "invalid import path %s - please use gopkgs.com/%s.v1 or see http://gopkgs.com/%s"
)
type goPkgsCheck struct{}
func init() {
typ := reflect.TypeOf(goPkgsCheck{})
if typ.PkgPath() == goPkgsSrcPath {
panic(fmt.Errorf(goPkgsErrFmt, typ.PkgPath(), goPkgsName, goPkgsName))
}
}

View File

@ -1,37 +0,0 @@
// +build go1.3,!appengine
package pool
import (
"sync"
)
// Pool is a thin compatibility type to allow Go
// libraries to use the new sync.Pool in Go 1.3,
// while remaining compatible with lower Go versions.
// For more information, see the sync.Pool type.
type Pool sync.Pool
// New returns a new Pool. The size argument is
// ignored on Go >= 1.3. In Go < 1.3, if size is
// zero, it's set to runtime.GOMAXPROCS(0) * 2.
func New(size int) *Pool {
return &Pool{}
}
// Get returns an arbitrary previously Put value, removing
// it from the pool, or nil if there are no such values. Note
// that callers should not assume anything about the Get return
// value, since the runtime might decide to collect the elements
// from the pool at any time.
//
// If there are no elements to return and the New() field is non-nil,
// Get returns the result of calling it.
func (p *Pool) Get() interface{} {
return (*sync.Pool)(p).Get()
}
// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
(*sync.Pool)(p).Put(x)
}

View File

@ -1,57 +0,0 @@
// +build !go1.3 appengine
package pool
import (
"runtime"
)
// Pool is a thin compatibility type to allow Go
// libraries to use the new sync.Pool in Go 1.3,
// while remaining compatible with lower Go versions.
// For more information, see the sync.Pool type.
type Pool struct {
ch chan interface{}
// New specifies a function to generate
// a new value, when Get would otherwise
// return nil.
New func() interface{}
}
// New returns a new Pool. The size argument is
// ignored on Go >= 1.3. In Go < 1.3, if size is
// zero, it's set to runtime.GOMAXPROCS(0) * 2.
func New(size int) *Pool {
if size == 0 {
size = runtime.GOMAXPROCS(0) * 2
}
return &Pool{ch: make(chan interface{}, size)}
}
// Get returns an arbitrary previously Put value, removing
// it from the pool, or nil if there are no such values. Note
// that callers should not assume anything about the Get return
// value, since the runtime might decide to collect the elements
// from the pool at any time.
//
// If there are no elements to return and the New() field is non-nil,
// Get returns the result of calling it.
func (p *Pool) Get() interface{} {
select {
case x := <-p.ch:
return x
default:
}
if p.New != nil {
return p.New()
}
return nil
}
// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
select {
case p.ch <- x:
default:
}
}

26
bower.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "grafana",
"version": "2.0.2",
"homepage": "https://github.com/grafana/grafana",
"authors": [],
"license": "Apache 2.0",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"public/vendor/",
"test",
"tests"
],
"dependencies": {
"jquery": "~2.1.4",
"angular": "~1.4.0",
"angular-route": "~1.4.0",
"angular-mocks": "~1.4.0",
"angular-sanitize": "~1.4.0",
"angular-native-dragdrop": "~1.1.0",
"angular-bindonce": "~0.3.3",
"requirejs": "~2.1.18",
"requirejs-text": "~2.0.14"
}
}

View File

@ -72,8 +72,9 @@ provider = file
# Provider config options
# memory: not have any config yet
# file: session dir path, is relative to grafana data_path
# redis: config like redis server addr, poolSize, password, e.g. `127.0.0.1:6379,100,grafana`
# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1)/database_name`
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=grafana`
# postgres: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
provider_config = sessions
@ -167,6 +168,10 @@ token_url = https://accounts.google.com/o/oauth2/token
api_url = https://www.googleapis.com/oauth2/v1/userinfo
allowed_domains =
#################################### Basic Auth ##########################
[auth.basic]
enabled = true
#################################### Auth Proxy ##########################
[auth.proxy]
enabled = false
@ -177,7 +182,7 @@ auto_sign_up = true
#################################### Auth LDAP ##########################
[auth.ldap]
enabled = true
hosts = ldap://localhost.com:389
hosts = ldap://127.0.0.1:389
use_ssl = false
base_dn = dc=grafana,dc=org
bind_path = cn=%username%,dc=grafana,dc=org
@ -186,6 +191,21 @@ attr_name = cn
attr_surname = sn
attr_email = email
#################################### SMTP / Emailing ##########################
[smtp]
enabled = false
host = localhost:25
user =
password =
cert_file =
key_file =
skip_verify = false
from_address = admin@grafana.localhost
[emails]
welcome_email_on_sign_up = false
templates_pattern = emails/*.html
#################################### Logging ##########################
[log]
# Either "console", "file", default is "console"

View File

@ -72,8 +72,9 @@
# Provider config options
# memory: not have any config yet
# file: session dir path, is relative to grafana data_path
# redis: config like redis server addr, poolSize, password, e.g. `127.0.0.1:6379,100,grafana`
# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1)/database_name`
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=grafana`
# mysql: go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
# postgres: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
;provider_config = sessions
# Session cookie name
@ -173,6 +174,24 @@
;header_property = username
;auto_sign_up = true
#################################### Basic Auth ##########################
[auth.basic]
;enabled = true
#################################### SMTP / Emailing ##########################
[smtp]
;enabled = false
;host = localhost:25
;user =
;password =
;cert_file =
;key_file =
;skip_verify = false
;from_address = admin@grafana.localhost
[emails]
;welcome_email_on_sign_up = false
#################################### Logging ##########################
[log]
# Either "console", "file", default is "console"

View File

@ -0,0 +1,13 @@
FROM centos:centos7
MAINTAINER Przemyslaw Ozgo <linux@ozgo.info>
RUN \
yum update -y && \
yum install -y net-snmp net-snmp-utils && \
yum clean all
COPY bootstrap.sh /tmp/bootstrap.sh
EXPOSE 161
ENTRYPOINT ["/tmp/bootstrap.sh"]

27
docker/blocks/smtp/bootstrap.sh Executable file
View File

@ -0,0 +1,27 @@
#!/bin/sh
set -u
# User params
USER_PARAMS=$@
# Internal params
RUN_CMD="snmpd -f ${USER_PARAMS}"
#######################################
# Echo/log function
# Arguments:
# String: value to log
#######################################
log() {
if [[ "$@" ]]; then echo "[`date +'%Y-%m-%d %T'`] $@";
else echo; fi
}
# Launch
log $RUN_CMD
$RUN_CMD
# Exit immidiately in case of any errors or when we have interactive terminal
if [[ $? != 0 ]] || test -t 0; then exit $?; fi
log

4
docker/blocks/smtp/fig Normal file
View File

@ -0,0 +1,4 @@
snmpd:
build: blocks/snmpd
ports:
- "161:161"

View File

@ -1 +1 @@
2.0.0-beta
2.1.0

View File

@ -45,7 +45,7 @@ pages:
- ['reference/graph.md', 'Reference', 'Graph Panel']
- ['reference/singlestat.md', 'Reference', 'Singlestat Panel']
- ['reference/dashlist.md', 'Reference', 'Dashlist Panel']
- ['reference/dashlist.md', 'Reference', 'Dashboard list Panel']
- ['reference/sharing.md', 'Reference', 'Sharing']
- ['reference/annotations.md', 'Reference', 'Annotations']
- ['reference/timerange.md', 'Reference', 'Time range controls']
@ -60,6 +60,7 @@ pages:
- ['datasources/graphite.md', 'Data Sources', 'Graphite']
- ['datasources/influxdb.md', 'Data Sources', 'InfluxDB']
- ['datasources/opentsdb.md', 'Data Sources', 'OpenTSDB']
- ['datasources/kairosdb.md', 'Data Sources', 'KairosDB']
- ['project/building_from_source.md', 'Project', 'Building from source']
- ['project/cla.md', 'Project', 'Contributor License Agreement']

View File

@ -6,9 +6,9 @@ page_keywords: grafana, graphite, metrics, query, documentation
# Graphite
Grafana has an advanced graphite query editor that lets you quickly navigate the metric space, add functions.
Change function paramaters and much more. The editor cannot handle all types of queries yet.
To switch to a regular text box click the pen icon to the right.
Grafana has an advanced Graphite query editor that lets you quickly navigate the metric space, add functions,
change function parameters and much more. The editor can handle all types of graphite queries. It can even handle complex nested
queries through the use of query references.
## Adding the data source to Grafana
Open the side menu by clicking the the Grafana icon in the top header. In the side menu under the `Dashboards` link you
@ -52,8 +52,21 @@ Some functions like aliasByNode support an optional second argument. To add this
## Point consolidation
All graphite metrics are consolidated so that graphite doesn't return more data points than there are pixels in the graph. By default
this consolidation is done using `avg` function. You can how graphite consolidates metrics by adding the Graphite consolidateBy function.
All Graphite metrics are consolidated so that Graphite doesn't return more data points than there are pixels in the graph. By default
this consolidation is done using `avg` function. You can how Graphite consolidates metrics by adding the Graphite consolidateBy function.
> *Notice* This means that legend summary values (max, min, total) cannot be all correct at the same time. They are calculated
> client side by Grafana. And depending on your consolidation function only one or two can be correct at the same time.
## Templating
You can create a template variable in Grafana and have that variable filled with values from any Graphite metric exploration query.
You can then use this variable in your Graphite queries, either as part of a metric path or as arguments to functions.
For example a query like `prod.servers.*` will fill the variable with all possible
values that exists in the wildcard position.
You can also create nested variables that use other variables in their definition. For example
`apps.$app.servers.*` uses the variable `$app` in its query definition.
![](/img/v2/templated_variable_parameter.png)

View File

@ -4,10 +4,11 @@ page_description: InfluxDB query guide
page_keywords: grafana, influxdb, metrics, query, documentation
---
# InfluxDB
There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0.8.x and InfluxDB 0.9.x. The API and capabilities of InfluxDB 0.9.x are completely different from InfluxDB 0.8.x. InfluxDB 0.9.x data source support is provided on an experimental basis.
There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0.8.x and InfluxDB 0.9.x.
The API and capabilities of InfluxDB 0.9.x are completely different from InfluxDB 0.8.x which is why Grafana handles
them as different data sources.
## Adding the data source to Grafana
Open the side menu by clicking the the Grafana icon in the top header. In the side menu under the `Dashboards` link you
@ -31,37 +32,73 @@ Password | Database user's password
> *Note* When using Proxy access mode the InfluxDB database, user and password will be hidden from the browser/frontend. When
> using direct access mode all users will be able to see the database user & password.
## InfluxDB 0.9.x query editor
## InfluxDB 0.9.x
This editor & data source is not compatible with InfluxDB 0.8.x, please use the right data source for you InfluxDB version.
The InfluxDB 0.9.x editor is currently under development and is not yet fully usable.
![](/img/influxdb/InfluxDB_09_editor.png)
## InfluxDB 0.8.x query editor
You find the InfluxDB editor in the metrics tab in Graph or Singlestat panel's edit mode. You enter edit mode by clicking the
panel title, then edit. The editor allows you to select metrics and tags.
### Editor tag filters
To add a tag filter click the plus icon to the right of the `WHERE` condition. You can remove tag filters by clicking on
the tag key and select `--remove tag filter--`.
### Regex matching
You can type in regex patterns for metric names or tag filter values, be sure to wrap the regex pattern in forward slashes (`/`). Grafana
will automaticallay adjust the filter tag condition to use the InfluxDB regex match condition operator (`=~`).
### Editor group by
To group by a tag click the plus icon after the `GROUP BY ($interval)` text. Pick a tag from the dropdown that appears.
You can remove the group by by clicking on the tag and then select `--remove group by--` from the dropdown.
### Editor RAW Query
You can switch to raw query mode by pressing the pen icon.
> If you use Raw Query be sure your query at minimum have `WHERE $timeFilter` clause and ends with `order by asc`.
> Also please always have a group by time and an aggregation function, otherwise InfluxDB can easily return hundreds of thousands
> of data points that will hang the browser.
### Alias patterns
- $m = replaced with measurement name
- $measurement = replaced with measurement name
- $tag_hostname = replaced with the value of the hostname tag
- You can also use [[tag_hostname]] pattern replacement syntax
### Templating
You can create a template variable in Grafana and have that variable filled with values from any InfluxDB metric exploration query.
You can then use this variable in your InfluxDB metric queries.
For example you can have a variable that contains all values for tag `hostname` if you specify a query like this
in the templating edit view.
```sql
SHOW TAG VALUES WITH KEY = "hostname"
```
You can also create nested variables. For example if you had another variable, for example `region`. Then you could have
the hosts variable only show hosts from the current selected region with a query like this:
```sql
SHOW TAG VALUES WITH KEY = "hostname" WHERE region =~ /$region/
```
> Always you `regex values` or `regex wildcard` for All format or multi select format.
![](/img/influxdb/templating_simple_ex1.png)
### Annotations
Annotations allows you to overlay rich event information on top of graphs.
An example query:
```SQL
SELECT title, description from events WHERE $timeFilter order asc
```
### InfluxDB 0.8.x
![](/img/v1/influxdb_editor.png)
When you add an InfluxDB query you can specify series name (can be regex), value column and a function. Group by time can be specified or if left blank will be automatically set depending on how long the current time span is. It will translate to a InfluxDB query that looks like this:
```sql
select [[func]]([[column]]) from [[series]] where [[timeFilter]] group by time([[interval]]) order asc
```
To write the complete query yourself click the cog wheel icon to the right and select ``Raw query mode``.
## InfluxDB 0.9 Filters & Templates queries
The InfluxDB 0.9 data source does not currently support filters or templates.
## InfluxDB 0.8 Filters & Templated queries
![](/img/animated_gifs/influxdb_templated_query.gif)
Use a distinct influxdb query in the filter query input box:
```sql
select distinct(host) from app.status
```

View File

@ -0,0 +1,47 @@
---
page_title: KairosDB Guide
page_description: KairosDB guide for Grafana
page_keywords: grafana, kairosdb, documentation
---
# KairosDB Guide
## Adding the data source to Grafana
Open the side menu by clicking the the Grafana icon in the top header. In the side menu under the `Dashboards` link you
should find a link named `Data Sources`. If this link is missing in the side menu it means that your current
user does not have the `Admin` role for the current organization.
<!-- ![](/img/v2/add_datasource_kairosdb.png) -->
Now click the `Add new` link in the top header.
Name | Description
------------ | -------------
Name | The data source name, important that this is the same as in Grafana v1.x if you plan to import old dashboards.
Default | Default data source means that it will be pre-selected for new panels.
Url | The http protocol, ip and port of your kairosdb server (default port is usually 8080)
Access | Proxy = access via Grafana backend, Direct = access directory from browser.
## Query editor
Open a graph in edit mode by click the title.
<!-- ![](/img/v2/kairosdb_query_editor.png) -->
For details on KairosDB metric queries checkout the offical.
- [Query Metrics - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/QueryMetrics.html).
## Templated queries
KairosDB Datasource Plugin provides following functions in `Variables values query` field in Templating Editor to query `metric names`, `tag names`, and `tag values` to kairosdb server.
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.
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)

View File

@ -27,6 +27,18 @@ Open a graph in edit mode by click the title.
![](/img/v2/opentsdb_query_editor.png)
### Auto complete suggestions
You should get auto complete suggestions for tags and tag values. If you do not you need to enable `tsd.core.meta.enable_realtime_ts` in
the OpentSDB server settings. This is required for the OpenTSDB `lookup` api to work.
## Templating queries
When using OpenTSDB with a template variable of `query` type you can use following syntax for lookup.
metrics() // returns metric names
tag_names(cpu) // return tag names (i.e. keys) for a specific cpu metric
tag_values(cpu, hostname) // return tag values for metric cpu and tag key hostname
For details on opentsdb metric queries checkout the official [OpenTSDB documentation](http://opentsdb.net/docs/build/html/index.html)

View File

@ -40,6 +40,14 @@ in the main Time Picker in the upper right, but they can also have relative time
5. Dashboard panel. You edit panels by clicking the panel title.
6. Graph legend. You can change series colors, y-axis and series visibility directly from the legend.
## Adding & Editing Graphs and Panels
![](/img/v2/graph_metrics_tab_graphite.png)
1. You add panels via row menu. The row menu is the green icon to the left of each row.
2. To edit the graph you click on the graph title to open the panel menu, then `Edit`.
3. This should take you to the `Metrics` tab. In this tab you should see the editor for your default data source.
## Drag-and-Drop panels
You can Drag-and-Drop Panels within and between Rows. Click and hold the Panel title, and drag it to its new location.

View File

@ -10,7 +10,7 @@ It provides a powerful and elegant way to create, share, and explore data and da
Grafana is most commonly used for Internet infrastructure and application analytics, but many use it in other domains including industrial sensors, home automation, weather, and process control.
Grafana features pluggable panels and data sources allowing easy extensibility. There is currently rich support for [Graphite](http://graphite.readthedocs.org/en/latest/), [InfluxDB](http://influxdb.org) and [OpenTSDB](http://opentsdb.net). There is also experimental support for KairosDB, and SQL is on the roadmap. Grafana has a variety of panels, including a fully featured graph panel with rich visualization options.
Grafana features pluggable panels and data sources allowing easy extensibility. There is currently rich support for [Graphite](http://graphite.readthedocs.org/en/latest/), [InfluxDB](http://influxdb.org) and [OpenTSDB](http://opentsdb.net). There is also experimental support for [KairosDB](https://github.com/kairosdb/kairosdb), and SQL is on the roadmap. Grafana has a variety of panels, including a fully featured graph panel with rich visualization options.
Version 2.0 was released in April 2015: Grafana now ships with its own backend server that brings [many changes and features](../guides/whats-new-in-v2/).

View File

@ -18,7 +18,7 @@ specified in a `.ini` configuration file or specified using environment variable
> **Note.** If you have installed Grafana using the `deb` or `rpm`
> packages, then your configuration file is located at
> `/etc/grafana/grafana.ini`. This path is specified in the Grafana
> init.d script using `--config` file parameter.
> init.d script using `-config` file parameter.
## Using environment variables
@ -28,14 +28,19 @@ using environment variables using the syntax:
GF_<SectionName>_<KeyName>
Where the section name is the text within the brackets. Everything
should be upper case. For example, given this configuration setting:
should be upper case, `.` should be replaced by `_`. For example, given these configuration settings:
[security]
admin_user = admin
[auth.google]
client_secret = 0ldS3cretKey
Then you can override that using:
export GF_SECURITY_ADMIN_USER=true
export GF_AUTH_GOOGLE_CLIENT_SECRET=newS3cretKey
<hr>
@ -322,7 +327,8 @@ This option should be configured differently depending on what type of
session provider you have configured.
- **file:** session file path, e.g. `data/sessions`
- **mysql:** go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1)/database_name`
- **mysql:** go-sql-driver/mysql dsn config string, e.g. `user:password@tcp(127.0.0.1:3306)/database_name`
- **postgres:** ex: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
If you use MySQL or Postgres as the session store you need to create the
session table manually.
@ -361,3 +367,14 @@ enabled. Counters are sent every 24 hours. Default value is `true`.
If you want to track Grafana usage via Google analytics specify *your* Universal Analytics ID
here. By default this feature is disabled.
## [dashboards.json]
If you have a system that automatically builds dashboards as json files you can enable this feature to have the
Grafana backend index those json dashboards which will make them appear in regular dashboard search.
### enabled
`true` or `false`. Is disabled by default.
### path
The full path to a directory containing your json dashboards.

View File

@ -24,7 +24,7 @@ container:
$ docker run -d -p 3000:3000 \
-v /var/lib/grafana:/var/lib/grafana \
-e "GF_SECURITY_ADMIN_PASSWORD=secret \
-e "GF_SECURITY_ADMIN_PASSWORD=secret" \
grafana/grafana:develop
In the above example I map the data folder and sets a configuration option via

View File

@ -58,6 +58,8 @@ bra run
Open grafana in your browser (default http://localhost:3000) and login with admin user (default user/pass = admin/admin).
## Creating optimized release packages
This step builds linux packages and requires that fpm is installed. Install fpm via `gem install fpm`.
```
go run build.go build package
```

View File

@ -13,7 +13,7 @@ you can get title, tags, and text information for the event.
To add an annotation query click dashboard settings icon in top menu and select `Annotations` from the
dropdown. This will open the `Annotations` edit view. Click the `Add` tab to add a new annotation query.
### Graphite annotations
## Graphite annotations
Graphite supports two ways to query annotations.
@ -36,5 +36,4 @@ as the name for the fields that should be used for the annotation title, tags an
For InfluxDB you need to enter a query like in the above screenshot. You need to have the ```where $timeFilter``` part.
If you only select one column you will not need to enter anything in the column mapping fields.
If you have multiple columns you need to specify which column should be treated as title, tags and text column.

View File

@ -6,4 +6,22 @@ page_keywords: grafana, dashlist, panel, documentation
# Dashlist Panel
## Overview
![](/img/v2/dashboard_list_panel.png)
The dashboard list panel allows you to show a list of links to other dashboards. The list
can be based on a search query or dashboard tag query. You can also configure it to show your starred
dashboards.
## Options
![](/img/v2/dashboard_list_panel_options.png)
Name | Description
------------ | -------------
Mode | Set search or starred mode
Query | If in search mode specify the search query
Tags | if in search mode specify dashboard tags to search for
Limit number to | Specify the maximum number of dashboards

View File

@ -183,7 +183,7 @@ Status Codes:
### Create data source
`PUT /api/datasources`
`POST /api/datasources`
**Example Response**:
@ -192,9 +192,9 @@ Status Codes:
{"message":"Datasource added"}
### Edit an existing data source
### Update an existing data source
`POST /api/datasources`
`PUT /api/datasources/:datasourceId`
### Delete an existing data source
@ -269,7 +269,7 @@ Adds a global user to the actual organisation.
### Delete User in Organisation
`DELETE /api/orgs/:orgId/users/:userId`
`DELETE /api/orgs/:orgId/users/:userId`
## Users

View File

@ -5,14 +5,43 @@ page_keywords: grafana, templating, variables, guide, documentation
---
# Templated Dashboards
![](/img/v2/templating_var_list.png)
Templating feature can be enabled under dashboard settings, in the Features tab. The templating feature allows
you to create variables that can be used in your metric queries, series names and panel titles. Use this feature to
create generic dashboards that can quickly be changed to show graphs for different servers or metrics.
## Overview
Templating allows you to create dashboard variables that can be used in your metric queries, series
names and panel titles. Use this feature to create generic dashboards that can quickly be
changed to show graphs for different servers or metrics.
You find this feature in the dashboard cog dropdown menu.
## Variable types
There are three different types of template variables. They can all be used in the
same way but they differ in how the list variables values is created.
### Query
This is the most common type of variable. It allows you to create a variable
with values fetched directly from a data source via a metric exploration query.
For example a query like `prod.servers.*` will fill the variable with all possible
values that exists in the wildcard position (Graphite example).
You can also create nested variables that use other variables in their definition. For example
`apps.$app.servers.*` uses the variable `$app` in its query definition.
> For examples of template queries appropriate for your data source checkout the documentation
> page for your data source.
### Interval
This variable type is useful for time ranges like `1m`,`1h`, `1d`. There is also an auto
option that will change depending on the current time range, you can specify how many times
the current time range should be divided to calculate the current `auto` range.
![](/img/v2/templated_variable_parameter.png)
### Custom
This variable type allow you to manually specify all the different values as a comma seperated
string.
## Screencast - Templated Graphite Queries
<iframe width="561" height="315" src="//www.youtube.com/embed/FhNUrueWwOk?list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2" frameborder="0" allowfullscreen></iframe>
<br>
## Screencast - Templated InfluxDB Queries
Coming soon

View File

@ -1,2 +1,3 @@
<li><a class='version' href='/v2.1'>Version v2.1</a></li>
<li><a class='version' href='/v2.0'>Version v2.0</a></li>
<li><a class='version' href='/v1.9'>Version v1.9</a></li>

View File

@ -14,8 +14,9 @@ import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/search"
"github.com/grafana/grafana/pkg/services/eventpublisher"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/social"
@ -57,6 +58,10 @@ func main() {
eventpublisher.Init()
plugins.Init()
if err := notifications.Init(); err != nil {
log.Fatal(3, "Notification service failed to initialize", err)
}
if setting.ReportingEnabled {
go metrics.StartUsageReportLoop()
}

View File

@ -62,6 +62,6 @@
"grunt-jscs": "~1.5.x",
"karma-sinon": "^1.0.3",
"lodash": "^2.4.1",
"sinon": "^1.10.3"
"sinon": "1.10.3"
}
}

View File

@ -43,9 +43,9 @@ case "$1" in
chmod 755 /var/log/grafana /var/lib/grafana
# configuration files should not be modifiable by grafana user, as this can be a security issue
chown -Rh root:root /etc/grafana/*
chown -Rh root:$GRAFANA_GROUP /etc/grafana/*
chmod 755 /etc/grafana
find /etc/grafana -type f -exec chmod 644 {} ';'
find /etc/grafana -type f -exec chmod 640 {} ';'
find /etc/grafana -type d -exec chmod 755 {} ';'
# if $2 is set, this is an upgrade

View File

@ -38,7 +38,12 @@ DAEMON=/usr/sbin/$NAME
if [ `id -u` -ne 0 ]; then
echo "You need root privileges to run this script"
exit 1
exit 4
fi
if [ ! -x $DAEMON ]; then
echo "Program not installed or not executable"
exit 5
fi
. /lib/lsb/init-functions
@ -54,9 +59,6 @@ fi
DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR}"
# Check DAEMON exists
test -x $DAEMON || exit 0
case "$1" in
start)
@ -137,8 +139,6 @@ case "$1" in
;;
*)
log_success_msg "Usage: $0 {start|stop|restart|force-reload|status}"
exit 1
exit 3
;;
esac
exit 0

View File

@ -43,9 +43,9 @@ if [ $1 -eq 1 ] ; then
chmod 755 /var/log/grafana /var/lib/grafana
# configuration files should not be modifiable by grafana user, as this can be a security issue
chown -Rh root:root /etc/grafana/*
chown -Rh root:$GRAFANA_GROUP /etc/grafana/*
chmod 755 /etc/grafana
find /etc/grafana -type f -exec chmod 644 {} ';'
find /etc/grafana -type f -exec chmod 640 {} ';'
find /etc/grafana -type d -exec chmod 755 {} ';'
if [ -x /bin/systemctl ] ; then

View File

@ -35,6 +35,16 @@ MAX_OPEN_FILES=10000
PID_FILE=/var/run/$NAME.pid
DAEMON=/usr/sbin/$NAME
if [ `id -u` -ne 0 ]; then
echo "You need root privileges to run this script"
exit 4
fi
if [ ! -x $DAEMON ]; then
echo "Program not installed or not executable"
exit 5
fi
#
# init.d / servicectl compatibility (openSUSE)
#
@ -55,9 +65,6 @@ fi
DAEMON_OPTS="--pidfile=${PID_FILE} --config=${CONF_FILE} cfg:default.paths.data=${DATA_DIR} cfg:default.paths.logs=${LOG_DIR}"
# Check DAEMON exists
test -x $DAEMON || exit 0
function isRunning() {
status -p $PID_FILE $NAME > /dev/null 2>&1
}
@ -69,7 +76,7 @@ case "$1" in
isRunning
if [ $? -eq 0 ]; then
echo "Already running."
exit 2
exit 0
fi
# Prepare environment
@ -90,7 +97,7 @@ case "$1" in
# check if pid file has been written two
if ! [[ -s $PID_FILE ]]; then
echo "FAILED"
exit 3
exit 1
fi
i=0
timeout=10
@ -101,7 +108,7 @@ case "$1" in
i=$(($i + 1))
if [ $i -gt $timeout ]; then
echo "FAILED"
exit 4
exit 1
fi
done
fi
@ -131,6 +138,7 @@ case "$1" in
;;
status)
status -p $PID_FILE $NAME
exit $?
;;
restart|force-reload)
if [ -f "$PID_FILE" ]; then
@ -141,6 +149,6 @@ case "$1" in
;;
*)
echo -n "Usage: $0 {start|stop|restart|force-reload|status}"
exit 1
exit 3
;;
esac

View File

@ -26,6 +26,7 @@ func Register(r *macaron.Macaron) {
// authed views
r.Get("/profile/", reqSignedIn, Index)
r.Get("/org/", reqSignedIn, Index)
r.Get("/org/new", reqSignedIn, Index)
r.Get("/datasources/", reqSignedIn, Index)
r.Get("/datasources/edit/*", reqSignedIn, Index)
r.Get("/org/users/", reqSignedIn, Index)
@ -39,7 +40,14 @@ func Register(r *macaron.Macaron) {
// sign up
r.Get("/signup", Index)
r.Post("/api/user/signup", bind(m.CreateUserCommand{}), SignUp)
r.Post("/api/user/signup", bind(m.CreateUserCommand{}), wrap(SignUp))
// reset password
r.Get("/user/password/send-reset-email", Index)
r.Get("/user/password/reset", Index)
r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))
// dashboard snapshots
r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)

View File

@ -87,10 +87,10 @@ func ApiError(status int, message string, err error) *NormalResponse {
switch status {
case 404:
resp["message"] = "Not Found"
metrics.M_Api_Status_500.Inc(1)
case 500:
metrics.M_Api_Status_404.Inc(1)
resp["message"] = "Not Found"
case 500:
metrics.M_Api_Status_500.Inc(1)
resp["message"] = "Internal Server Error"
}

View File

@ -4,13 +4,14 @@ import (
"encoding/json"
"os"
"path"
"strings"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/search"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@ -31,7 +32,7 @@ func isDasboardStarredByUser(c *middleware.Context, dashId int64) (bool, error)
func GetDashboard(c *middleware.Context) {
metrics.M_Api_Dashboard_Get.Inc(1)
slug := c.Params(":slug")
slug := strings.ToLower(c.Params(":slug"))
query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
err := bus.Dispatch(&query)

View File

@ -59,7 +59,7 @@ func GetDashboardSnapshot(c *middleware.Context) {
// expired snapshots should also be removed from db
if snapshot.Expires.Before(time.Now()) {
c.JsonApiErr(404, "Snapshot not found", err)
c.JsonApiErr(404, "Dashboard snapshot not found", err)
return
}

View File

@ -18,7 +18,7 @@ type AdminUpdateUserPasswordForm struct {
}
type AdminUpdateUserPermissionsForm struct {
IsGrafanaAdmin bool `json:"IsGrafanaAdmin" binding:"Required"`
IsGrafanaAdmin bool `json:"IsGrafanaAdmin"`
}
type AdminUserListItem struct {
@ -27,3 +27,13 @@ type AdminUserListItem struct {
Login string `json:"login"`
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
}
type SendResetPasswordEmailForm struct {
UserOrEmail string `json:"userOrEmail" binding:"Required"`
}
type ResetUserPasswordForm struct {
Code string `json:"code"`
NewPassword string `json:"newPassword"`
ConfirmPassword string `json:"confirmPassword"`
}

View File

@ -99,7 +99,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
"defaultDatasource": defaultDatasource,
"datasources": datasources,
"appSubUrl": setting.AppSubUrl,
"viewerRoleMode": setting.ViewerRoleMode,
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"buildInfo": map[string]interface{}{
"version": setting.BuildVersion,
"commit": setting.BuildCommit,

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/gogits/gogs/modules/ldap"
"github.com/go-ldap/ldap"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
)
@ -15,7 +15,7 @@ var (
)
func Login(username, password string) error {
url, err := url.Parse(setting.LdapUrls[0])
url, err := url.Parse(setting.LdapHosts[0])
if err != nil {
return err
}

View File

@ -4,7 +4,6 @@ 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"
@ -89,28 +88,20 @@ func LoginApiPing(c *middleware.Context) {
}
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)
authQuery := auth.AuthenticateUserQuery{
Username: cmd.User,
Password: cmd.Password,
}
var err error
var user *m.User
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 := bus.Dispatch(&authQuery); err != nil {
if err == auth.ErrInvalidCredentials {
return ApiError(401, "Invalid username or password", err)
}
return ApiError(500, "Error while trying to authenticate user", err)
}
if err != nil {
return ApiError(401, "Invalid username or password", err)
}
user := authQuery.User
loginUserWithUser(user, c)
@ -128,19 +119,6 @@ func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response {
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) {
if user == nil {
log.Error(3, "User login with nil user")

View File

@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
// GET /api/org
@ -39,7 +40,7 @@ func getOrgHelper(orgId int64) Response {
// POST /api/orgs
func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) Response {
if !setting.AllowUserOrgCreate && !c.IsGrafanaAdmin {
if !c.IsSignedIn || (!setting.AllowUserOrgCreate && !c.IsGrafanaAdmin) {
return ApiError(401, "Access denied", nil)
}
@ -50,7 +51,10 @@ func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) Response {
metrics.M_Api_Org_Create.Inc(1)
return ApiSuccess("Organization created")
return Json(200, &util.DynMap{
"orgId": cmd.Result.Id,
"message": "Organization created",
})
}
// PUT /api/org

49
pkg/api/password.go Normal file
View File

@ -0,0 +1,49 @@
package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util"
)
func SendResetPasswordEmail(c *middleware.Context, form dtos.SendResetPasswordEmailForm) Response {
userQuery := m.GetUserByLoginQuery{LoginOrEmail: form.UserOrEmail}
if err := bus.Dispatch(&userQuery); err != nil {
return ApiError(404, "User does not exist", err)
}
emailCmd := m.SendResetPasswordEmailCommand{User: userQuery.Result}
if err := bus.Dispatch(&emailCmd); err != nil {
return ApiError(500, "Failed to send email", err)
}
return ApiSuccess("Email sent")
}
func ResetPassword(c *middleware.Context, form dtos.ResetUserPasswordForm) Response {
query := m.ValidateResetPasswordCodeQuery{Code: form.Code}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrInvalidEmailCode {
return ApiError(400, "Invalid or expired reset password code", nil)
}
return ApiError(500, "Unknown error validating email code", err)
}
if form.NewPassword != form.ConfirmPassword {
return ApiError(400, "Passwords do not match", nil)
}
cmd := m.ChangeUserPasswordCommand{}
cmd.UserId = query.Result.Id
cmd.NewPassword = util.EncodePassword(form.NewPassword, query.Result.Salt)
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to change user password", err)
}
return ApiSuccess("User password changed")
}

View File

@ -3,7 +3,7 @@ package api
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/search"
"github.com/grafana/grafana/pkg/services/search"
)
func Search(c *middleware.Context) {

View File

@ -2,6 +2,7 @@ package api
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
@ -9,24 +10,29 @@ import (
)
// POST /api/user/signup
func SignUp(c *middleware.Context, cmd m.CreateUserCommand) {
func SignUp(c *middleware.Context, cmd m.CreateUserCommand) Response {
if !setting.AllowUserSignUp {
c.JsonApiErr(401, "User signup is disabled", nil)
return
return ApiError(401, "User signup is disabled", nil)
}
cmd.Login = cmd.Email
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "failed to create user", err)
return
return ApiError(500, "failed to create user", err)
}
user := cmd.Result
bus.Publish(&events.UserSignedUp{
Id: user.Id,
Name: user.Name,
Email: user.Email,
Login: user.Login,
})
loginUserWithUser(&user, c)
c.JsonOK("User created and logged in")
metrics.M_Api_User_SignUp.Inc(1)
return ApiSuccess("User created and logged in")
}

View File

@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@ -38,36 +39,46 @@ type AuthSource interface {
AuthenticateUser(username, password string) (*m.User, error)
}
type GetAuthSourcesQuery struct {
Sources []AuthSource
type AuthenticateUserQuery struct {
Username string
Password string
User *m.User
}
func init() {
bus.AddHandler("auth", GetAuthSources)
bus.AddHandler("auth", AuthenticateUser)
}
func GetAuthSources(query *GetAuthSourcesQuery) error {
query.Sources = []AuthSource{&GrafanaDBAuthSource{}}
return nil
func AuthenticateUser(query *AuthenticateUserQuery) error {
err := loginUsingGrafanaDB(query)
if err == nil || err != ErrInvalidCredentials {
return err
}
if setting.LdapEnabled {
err = loginUsingLdap(query)
}
return err
}
type GrafanaDBAuthSource struct {
}
func loginUsingGrafanaDB(query *AuthenticateUserQuery) error {
userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username}
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
if err := bus.Dispatch(&userQuery); err != nil {
if err == m.ErrUserNotFound {
return ErrInvalidCredentials
}
return err
}
user := userQuery.Result
passwordHashed := util.EncodePassword(password, user.Salt)
passwordHashed := util.EncodePassword(query.Password, user.Salt)
if passwordHashed != user.Password {
return nil, ErrInvalidCredentials
return ErrInvalidCredentials
}
return user, nil
query.User = user
return nil
}

55
pkg/auth/ldap.go Normal file
View File

@ -0,0 +1,55 @@
package auth
import (
"fmt"
"net/url"
"github.com/go-ldap/ldap"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
func loginUsingLdap(query *AuthenticateUserQuery) error {
url, err := url.Parse(setting.LdapHosts[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, query.Username)
err = conn.Bind(nx, query.Password)
if err != nil {
if ldapErr, ok := err.(*ldap.Error); ok {
if ldapErr.ResultCode == 49 {
return ErrInvalidCredentials
}
}
return err
}
userQuery := m.GetUserByLoginQuery{LoginOrEmail: "admin"}
err = bus.Dispatch(&userQuery)
if err != nil {
if err == m.ErrUserNotFound {
return ErrInvalidCredentials
}
return err
}
query.User = userQuery.Result
return nil
}

View File

@ -1,7 +1,7 @@
package bus
import (
"errors"
"fmt"
"reflect"
)
@ -39,7 +39,7 @@ func (b *InProcBus) Dispatch(msg Msg) error {
var handler = b.handlers[msgName]
if handler == nil {
return errors.New("handler not found")
return fmt.Errorf("handler not found for %s", msgName)
}
var params = make([]reflect.Value, 1)

View File

@ -33,6 +33,7 @@ func newMacaron() *macaron.Macaron {
mapStatic(m, "css", "css")
mapStatic(m, "img", "img")
mapStatic(m, "fonts", "fonts")
mapStatic(m, "robots.txt", "robots.txxt")
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: path.Join(setting.StaticRootPath, "views"),

View File

@ -1,27 +0,0 @@
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.

View File

@ -1,33 +0,0 @@
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

View File

@ -1,63 +0,0 @@
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

View File

@ -1,89 +0,0 @@
// 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)
}

View File

@ -1,52 +0,0 @@
// 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)
}

View File

@ -1,45 +0,0 @@
// 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)
}

View File

@ -1,45 +0,0 @@
// 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)
}

View File

@ -1,67 +0,0 @@
#
# 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

View File

@ -1,55 +0,0 @@
// 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
}

View File

@ -1,275 +0,0 @@
// 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
}
}
}

View File

@ -1,157 +0,0 @@
// 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
}

View File

@ -1,24 +0,0 @@
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)
}
}

View File

@ -1,248 +0,0 @@
// 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
}
}

View File

@ -1,78 +0,0 @@
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])
}
}

View File

@ -1,302 +0,0 @@
// 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"
}

View File

@ -1,123 +0,0 @@
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))
}
}
}

View File

@ -1,156 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// File contains Modify functionality
//
// https://tools.ietf.org/html/rfc4511
//
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
// object LDAPDN,
// changes SEQUENCE OF change SEQUENCE {
// operation ENUMERATED {
// add (0),
// delete (1),
// replace (2),
// ... },
// modification PartialAttribute } }
//
// PartialAttribute ::= SEQUENCE {
// type AttributeDescription,
// vals SET OF value AttributeValue }
//
// AttributeDescription ::= LDAPString
// -- Constrained to <attributedescription>
// -- [RFC4512]
//
// AttributeValue ::= OCTET STRING
//
package ldap
import (
"errors"
"log"
"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
}

View File

@ -1,350 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// File contains Search functionality
//
// https://tools.ietf.org/html/rfc4511
//
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
// baseObject LDAPDN,
// scope ENUMERATED {
// baseObject (0),
// singleLevel (1),
// wholeSubtree (2),
// ... },
// derefAliases ENUMERATED {
// neverDerefAliases (0),
// derefInSearching (1),
// derefFindingBaseObj (2),
// derefAlways (3) },
// sizeLimit INTEGER (0 .. maxInt),
// timeLimit INTEGER (0 .. maxInt),
// typesOnly BOOLEAN,
// filter Filter,
// attributes AttributeSelection }
//
// AttributeSelection ::= SEQUENCE OF selector LDAPString
// -- The LDAPString is constrained to
// -- <attributeSelector> in Section 4.5.1.8
//
// Filter ::= CHOICE {
// and [0] SET SIZE (1..MAX) OF filter Filter,
// or [1] SET SIZE (1..MAX) OF filter Filter,
// not [2] Filter,
// equalityMatch [3] AttributeValueAssertion,
// substrings [4] SubstringFilter,
// greaterOrEqual [5] AttributeValueAssertion,
// lessOrEqual [6] AttributeValueAssertion,
// present [7] AttributeDescription,
// approxMatch [8] AttributeValueAssertion,
// extensibleMatch [9] MatchingRuleAssertion,
// ... }
//
// SubstringFilter ::= SEQUENCE {
// type AttributeDescription,
// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
// initial [0] AssertionValue, -- can occur at most once
// any [1] AssertionValue,
// final [2] AssertionValue } -- can occur at most once
// }
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
//
//
package ldap
import (
"errors"
"fmt"
"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
}

View File

@ -54,7 +54,7 @@ func RenderToPng(params *RenderOpts) (string, error) {
}()
select {
case <-time.After(10 * time.Second):
case <-time.After(15 * time.Second):
if err := cmd.Process.Kill(); err != nil {
log.Error(4, "failed to kill: %v", err)
}

View File

@ -5,7 +5,7 @@ import (
"time"
)
// Events can be passed to external systems via for example AMPQ
// Events can be passed to external systems via for example AMQP
// Treat these events as basically DTOs so changes has to be backward compatible
type Priority string
@ -70,6 +70,14 @@ type UserCreated struct {
Email string `json:"email"`
}
type UserSignedUp struct {
Timestamp time.Time `json:"timestamp"`
Id int64 `json:"id"`
Name string `json:"name"`
Login string `json:"login"`
Email string `json:"email"`
}
type UserUpdated struct {
Timestamp time.Time `json:"timestamp"`
Id int64 `json:"id"`

View File

@ -60,8 +60,10 @@ func getCreateUserCommandForProxyAuth(headerVal string) *m.CreateUserCommand {
cmd := m.CreateUserCommand{}
if setting.AuthProxyHeaderProperty == "username" {
cmd.Login = headerVal
cmd.Email = headerVal
} else if setting.AuthProxyHeaderProperty == "email" {
cmd.Email = headerVal
cmd.Login = headerVal
} else {
panic("Auth proxy header property invalid")
}

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
type Context struct {
@ -40,6 +41,7 @@ func GetContextHandler() macaron.Handler {
// then look for api key in session (special case for render calls via api)
// then test if anonymous access is enabled
if initContextWithApiKey(ctx) ||
initContextWithBasicAuth(ctx) ||
initContextWithAuthProxy(ctx) ||
initContextWithUserSessionCookie(ctx) ||
initContextWithApiKeyFromSession(ctx) ||
@ -128,6 +130,47 @@ func initContextWithApiKey(ctx *Context) bool {
}
}
func initContextWithBasicAuth(ctx *Context) bool {
if !setting.BasicAuthEnabled {
return false
}
header := ctx.Req.Header.Get("Authorization")
if header == "" {
return false
}
username, password, err := util.DecodeBasicAuthHeader(header)
if err != nil {
ctx.JsonApiErr(401, "Invalid Basic Auth Header", err)
return true
}
loginQuery := m.GetUserByLoginQuery{LoginOrEmail: username}
if err := bus.Dispatch(&loginQuery); err != nil {
ctx.JsonApiErr(401, "Basic auth failed", err)
return true
}
user := loginQuery.Result
// validate password
if util.EncodePassword(password, user.Salt) != user.Password {
ctx.JsonApiErr(401, "Invalid username or password", nil)
return true
}
query := m.GetSignedInUserQuery{UserId: user.Id}
if err := bus.Dispatch(&query); err != nil {
ctx.JsonApiErr(401, "Authentication error", err)
return true
} else {
ctx.SignedInUser = query.Result
ctx.IsSignedIn = true
return true
}
}
// special case for panel render calls with api key
func initContextWithApiKeyFromSession(ctx *Context) bool {
keyId := ctx.Session.Get(SESS_KEY_APIKEY)
@ -197,10 +240,10 @@ func (ctx *Context) JsonApiErr(status int, message string, err error) {
switch status {
case 404:
resp["message"] = "Not Found"
metrics.M_Api_Status_500.Inc(1)
case 500:
metrics.M_Api_Status_404.Inc(1)
resp["message"] = "Not Found"
case 500:
metrics.M_Api_Status_500.Inc(1)
resp["message"] = "Internal Server Error"
}

View File

@ -48,6 +48,32 @@ func TestMiddlewareContext(t *testing.T) {
})
})
middlewareScenario("Using basic auth", func(sc *scenarioContext) {
bus.AddHandler("test", func(query *m.GetUserByLoginQuery) error {
query.Result = &m.User{
Password: util.EncodePassword("myPass", "salt"),
Salt: "salt",
}
return nil
})
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 2, UserId: 12}
return nil
})
setting.BasicAuthEnabled = true
authHeader := util.GetBasicAuthHeader("myUser", "myPass")
sc.fakeReq("GET", "/").withAuthoriziationHeader(authHeader).exec()
Convey("Should init middleware context with user", func() {
So(sc.context.IsSignedIn, ShouldEqual, true)
So(sc.context.OrgId, ShouldEqual, 2)
So(sc.context.UserId, ShouldEqual, 12)
})
})
middlewareScenario("Valid api key", func(sc *scenarioContext) {
keyhash := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd")
@ -223,6 +249,7 @@ type scenarioContext struct {
context *Context
resp *httptest.ResponseRecorder
apiKey string
authHeader string
respJson map[string]interface{}
handlerFunc handlerFunc
defaultHandler macaron.Handler
@ -240,6 +267,11 @@ func (sc *scenarioContext) withInvalidApiKey() *scenarioContext {
return sc
}
func (sc *scenarioContext) withAuthoriziationHeader(authHeader string) *scenarioContext {
sc.authHeader = authHeader
return sc
}
func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
sc.resp = httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil)
@ -266,6 +298,10 @@ func (sc *scenarioContext) exec() {
sc.req.Header.Add("Authorization", "Bearer "+sc.apiKey)
}
if sc.authHeader != "" {
sc.req.Header.Add("Authorization", sc.authHeader)
}
sc.m.ServeHTTP(sc.resp, sc.req)
if sc.resp.Header().Get("Content-Type") == "application/json; charset=UTF-8" {

View File

@ -5,12 +5,13 @@ import (
"strings"
"time"
"github.com/dalu/slug"
"github.com/gosimple/slug"
)
// Typed errors
var (
ErrDashboardNotFound = errors.New("Dashboard not found")
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
ErrDashboardWithSameNameExists = errors.New("A dashboard with the same name already exists")
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
)
@ -49,7 +50,7 @@ func NewDashboard(title string) *Dashboard {
// GetTags turns the tags in data json into go string array
func (dash *Dashboard) GetTags() []string {
jsonTags := dash.Data["tags"]
if jsonTags == nil {
if jsonTags == nil || jsonTags == "" {
return []string{}
}

View File

@ -15,4 +15,17 @@ func TestDashboardModel(t *testing.T) {
So(dashboard.Slug, ShouldEqual, "grafana-play-home")
})
Convey("Given a dashboard json", t, func() {
json := map[string]interface{}{
"title": "test dash",
}
Convey("With tags as string value", func() {
json["tags"] = ""
dash := NewDashboardFromJson(json)
So(len(dash.GetTags()), ShouldEqual, 0)
})
})
}

22
pkg/models/emails.go Normal file
View File

@ -0,0 +1,22 @@
package models
import "errors"
var ErrInvalidEmailCode = errors.New("Invalid or expired email code")
type SendEmailCommand struct {
To []string
Template string
Data map[string]interface{}
Massive bool
Info string
}
type SendResetPasswordEmailCommand struct {
User *User
}
type ValidateResetPasswordCodeQuery struct {
Code string
Result *User
}

View File

@ -1,7 +1,5 @@
package models
import "errors"
type OAuthType int
const (
@ -9,5 +7,3 @@ const (
GOOGLE
TWITTER
)
var ErrNotFound = errors.New("Not found")

View File

@ -30,6 +30,16 @@ type User struct {
Updated time.Time
}
func (u *User) NameOrFallback() string {
if u.Name != "" {
return u.Name
} else if u.Login != "" {
return u.Login
} else {
return u.Email
}
}
// ---------------------
// COMMANDS

Some files were not shown because too many files have changed in this diff Show More