Sfoglia il codice sorgente

Started work on LDAP again, #1450

Torkel Ödegaard 10 anni fa
parent
commit
0ef7271326
100 ha cambiato i file con 757 aggiunte e 2754 eliminazioni
  1. 3 0
      .bowerrc
  2. 13 0
      CHANGELOG.md
  3. 9 13
      Godeps/Godeps.json
  4. 0 6
      Godeps/_workspace/src/github.com/dalu/unidecode/README.md
  5. 2 0
      Godeps/_workspace/src/github.com/gosimple/slug/.gitignore
  6. 7 3
      Godeps/_workspace/src/github.com/gosimple/slug/README.md
  7. 0 0
      Godeps/_workspace/src/github.com/gosimple/slug/default_substitution.go
  8. 5 1
      Godeps/_workspace/src/github.com/gosimple/slug/doc.go
  9. 0 0
      Godeps/_workspace/src/github.com/gosimple/slug/languages_substitution.go
  10. 2 1
      Godeps/_workspace/src/github.com/gosimple/slug/slug.go
  11. 0 0
      Godeps/_workspace/src/github.com/gosimple/slug/slug_test.go
  12. 0 0
      Godeps/_workspace/src/github.com/rainycape/unidecode/.gitignore
  13. 0 0
      Godeps/_workspace/src/github.com/rainycape/unidecode/LICENSE
  14. 6 0
      Godeps/_workspace/src/github.com/rainycape/unidecode/README.md
  15. 0 3
      Godeps/_workspace/src/github.com/rainycape/unidecode/decode.go
  16. 0 0
      Godeps/_workspace/src/github.com/rainycape/unidecode/make_table.go
  17. 0 0
      Godeps/_workspace/src/github.com/rainycape/unidecode/table.go
  18. 0 0
      Godeps/_workspace/src/github.com/rainycape/unidecode/table.txt
  19. 4 11
      Godeps/_workspace/src/github.com/rainycape/unidecode/unidecode.go
  20. 0 0
      Godeps/_workspace/src/github.com/rainycape/unidecode/unidecode_test.go
  21. 0 23
      Godeps/_workspace/src/gopkgs.com/pool.v1/.gitignore
  22. 0 201
      Godeps/_workspace/src/gopkgs.com/pool.v1/LICENSE
  23. 0 13
      Godeps/_workspace/src/gopkgs.com/pool.v1/README.md
  24. 0 3
      Godeps/_workspace/src/gopkgs.com/pool.v1/doc.go
  25. 0 23
      Godeps/_workspace/src/gopkgs.com/pool.v1/example_test.go
  26. 0 24
      Godeps/_workspace/src/gopkgs.com/pool.v1/gopkgs.go
  27. 0 37
      Godeps/_workspace/src/gopkgs.com/pool.v1/pool.go
  28. 0 57
      Godeps/_workspace/src/gopkgs.com/pool.v1/pool_go1.2.go
  29. 26 0
      bower.json
  30. 23 3
      conf/defaults.ini
  31. 21 2
      conf/sample.ini
  32. 13 0
      docker/blocks/smtp/Dockerfile
  33. 27 0
      docker/blocks/smtp/bootstrap.sh
  34. 4 0
      docker/blocks/smtp/fig
  35. 1 1
      docs/VERSION
  36. 2 1
      docs/mkdocs.yml
  37. 18 5
      docs/sources/datasources/graphite.md
  38. 55 18
      docs/sources/datasources/influxdb.md
  39. 47 0
      docs/sources/datasources/kairosdb.md
  40. 12 0
      docs/sources/datasources/opentsdb.md
  41. 8 0
      docs/sources/guides/gettingstarted.md
  42. 1 1
      docs/sources/index.md
  43. 20 3
      docs/sources/installation/configuration.md
  44. 1 1
      docs/sources/installation/docker.md
  45. 2 0
      docs/sources/project/building_from_source.md
  46. 1 2
      docs/sources/reference/annotations.md
  47. 18 0
      docs/sources/reference/dashlist.md
  48. 4 4
      docs/sources/reference/http_api.md
  49. 35 6
      docs/sources/reference/templating.md
  50. 1 0
      docs/sources/versions.html_fragment
  51. 6 1
      main.go
  52. 1 1
      package.json
  53. 2 2
      packaging/deb/control/postinst
  54. 7 7
      packaging/deb/init.d/grafana-server
  55. 2 2
      packaging/rpm/control/postinst
  56. 15 7
      packaging/rpm/init.d/grafana-server
  57. 9 1
      pkg/api/api.go
  58. 2 2
      pkg/api/common.go
  59. 3 2
      pkg/api/dashboard.go
  60. 1 1
      pkg/api/dashboard_snapshot.go
  61. 11 1
      pkg/api/dtos/user.go
  62. 1 1
      pkg/api/frontendsettings.go
  63. 2 2
      pkg/api/ldapauth/ldapauth.go
  64. 9 31
      pkg/api/login.go
  65. 6 2
      pkg/api/org.go
  66. 49 0
      pkg/api/password.go
  67. 1 1
      pkg/api/search.go
  68. 13 7
      pkg/api/signup.go
  69. 27 16
      pkg/auth/auth.go
  70. 55 0
      pkg/auth/ldap.go
  71. 2 2
      pkg/bus/bus.go
  72. 1 0
      pkg/cmd/web.go
  73. 0 27
      pkg/components/ldap/LICENSE
  74. 0 33
      pkg/components/ldap/README
  75. 0 63
      pkg/components/ldap/_examples/enterprise.ldif
  76. 0 89
      pkg/components/ldap/_examples/modify.go
  77. 0 52
      pkg/components/ldap/_examples/search.go
  78. 0 45
      pkg/components/ldap/_examples/searchSSL.go
  79. 0 45
      pkg/components/ldap/_examples/searchTLS.go
  80. 0 67
      pkg/components/ldap/_examples/slapd.conf
  81. 0 55
      pkg/components/ldap/bind.go
  82. 0 275
      pkg/components/ldap/conn.go
  83. 0 157
      pkg/components/ldap/control.go
  84. 0 24
      pkg/components/ldap/debug.go
  85. 0 248
      pkg/components/ldap/filter.go
  86. 0 78
      pkg/components/ldap/filter_test.go
  87. 0 302
      pkg/components/ldap/ldap.go
  88. 0 123
      pkg/components/ldap/ldap_test.go
  89. 0 156
      pkg/components/ldap/modify.go
  90. 0 350
      pkg/components/ldap/search.go
  91. 1 1
      pkg/components/renderer/renderer.go
  92. 9 1
      pkg/events/events.go
  93. 2 0
      pkg/middleware/auth_proxy.go
  94. 45 2
      pkg/middleware/middleware.go
  95. 36 0
      pkg/middleware/middleware_test.go
  96. 3 2
      pkg/models/dashboards.go
  97. 13 0
      pkg/models/dashboards_test.go
  98. 22 0
      pkg/models/emails.go
  99. 0 4
      pkg/models/models.go
  100. 10 0
      pkg/models/user.go

+ 3 - 0
.bowerrc

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

+ 13 - 0
CHANGELOG.md

@@ -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**

+ 9 - 13
Godeps/Godeps.json

@@ -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"
 		}
 	]
 }

+ 0 - 6
Godeps/_workspace/src/github.com/dalu/unidecode/README.md

@@ -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

+ 2 - 0
Godeps/_workspace/src/github.com/gosimple/slug/.gitignore

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

+ 7 - 3
Godeps/_workspace/src/github.com/dalu/slug/README.md → Godeps/_workspace/src/github.com/gosimple/slug/README.md

@@ -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
 

+ 0 - 0
Godeps/_workspace/src/github.com/dalu/slug/default_substitution.go → Godeps/_workspace/src/github.com/gosimple/slug/default_substitution.go


+ 5 - 1
Godeps/_workspace/src/github.com/dalu/slug/doc.go → Godeps/_workspace/src/github.com/gosimple/slug/doc.go

@@ -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

+ 0 - 0
Godeps/_workspace/src/github.com/dalu/slug/languages_substitution.go → Godeps/_workspace/src/github.com/gosimple/slug/languages_substitution.go


+ 2 - 1
Godeps/_workspace/src/github.com/dalu/slug/slug.go → Godeps/_workspace/src/github.com/gosimple/slug/slug.go

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

+ 0 - 0
Godeps/_workspace/src/github.com/dalu/slug/slug_test.go → Godeps/_workspace/src/github.com/gosimple/slug/slug_test.go


+ 0 - 0
Godeps/_workspace/src/github.com/dalu/unidecode/.gitignore → Godeps/_workspace/src/github.com/rainycape/unidecode/.gitignore


+ 0 - 0
Godeps/_workspace/src/github.com/dalu/unidecode/LICENSE → Godeps/_workspace/src/github.com/rainycape/unidecode/LICENSE


+ 6 - 0
Godeps/_workspace/src/github.com/rainycape/unidecode/README.md

@@ -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)

+ 0 - 3
Godeps/_workspace/src/github.com/dalu/unidecode/decode.go → Godeps/_workspace/src/github.com/rainycape/unidecode/decode.go

@@ -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

+ 0 - 0
Godeps/_workspace/src/github.com/dalu/unidecode/make_table.go → Godeps/_workspace/src/github.com/rainycape/unidecode/make_table.go


+ 0 - 0
Godeps/_workspace/src/github.com/dalu/unidecode/table.go → Godeps/_workspace/src/github.com/rainycape/unidecode/table.go


+ 0 - 0
Godeps/_workspace/src/github.com/dalu/unidecode/table.txt → Godeps/_workspace/src/github.com/rainycape/unidecode/table.txt


+ 4 - 11
Godeps/_workspace/src/github.com/dalu/unidecode/unidecode.go → Godeps/_workspace/src/github.com/rainycape/unidecode/unidecode.go

@@ -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 {

+ 0 - 0
Godeps/_workspace/src/github.com/dalu/unidecode/unidecode_test.go → Godeps/_workspace/src/github.com/rainycape/unidecode/unidecode_test.go


+ 0 - 23
Godeps/_workspace/src/gopkgs.com/pool.v1/.gitignore

@@ -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

+ 0 - 201
Godeps/_workspace/src/gopkgs.com/pool.v1/LICENSE

@@ -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.

+ 0 - 13
Godeps/_workspace/src/gopkgs.com/pool.v1/README.md

@@ -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

+ 0 - 3
Godeps/_workspace/src/gopkgs.com/pool.v1/doc.go

@@ -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

+ 0 - 23
Godeps/_workspace/src/gopkgs.com/pool.v1/example_test.go

@@ -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!
-}

+ 0 - 24
Godeps/_workspace/src/gopkgs.com/pool.v1/gopkgs.go

@@ -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))
-	}
-}

+ 0 - 37
Godeps/_workspace/src/gopkgs.com/pool.v1/pool.go

@@ -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)
-}

+ 0 - 57
Godeps/_workspace/src/gopkgs.com/pool.v1/pool_go1.2.go

@@ -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 - 0
bower.json

@@ -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"
+  }
+}

+ 23 - 3
conf/defaults.ini

@@ -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"

+ 21 - 2
conf/sample.ini

@@ -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"

+ 13 - 0
docker/blocks/smtp/Dockerfile

@@ -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 - 0
docker/blocks/smtp/bootstrap.sh

@@ -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 - 0
docker/blocks/smtp/fig

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

+ 1 - 1
docs/VERSION

@@ -1 +1 @@
-2.0.0-beta
+2.1.0

+ 2 - 1
docs/mkdocs.yml

@@ -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']

+ 18 - 5
docs/sources/datasources/graphite.md

@@ -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)
+

+ 55 - 18
docs/sources/datasources/influxdb.md

@@ -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.
 
-![](/img/v1/influxdb_editor.png)
+### 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--`.
 
-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:
+### 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 (`=~`).
 
-```sql
-select [[func]]([[column]]) from [[series]] where [[timeFilter]] group by time([[interval]]) order asc
-```
+### 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.
 
-To write the complete query yourself click the cog wheel icon to the right and select ``Raw query mode``.
+### Editor RAW Query
+You can switch to raw query mode by pressing the pen icon.
 
-## InfluxDB 0.9 Filters & Templates queries
+> 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.
 
-The InfluxDB 0.9 data source does not currently support filters or templates.
+### Alias patterns
 
-## InfluxDB 0.8 Filters & Templated queries
+- $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
 
-![](/img/animated_gifs/influxdb_templated_query.gif)
+### 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"
+```
 
-Use a distinct influxdb query in the filter query input box:
+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
-select distinct(host) from app.status
+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)
+
+
 
 

+ 47 - 0
docs/sources/datasources/kairosdb.md

@@ -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)

+ 12 - 0
docs/sources/datasources/opentsdb.md

@@ -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)
 
 

+ 8 - 0
docs/sources/guides/gettingstarted.md

@@ -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.

+ 1 - 1
docs/sources/index.md

@@ -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/).
 

+ 20 - 3
docs/sources/installation/configuration.md

@@ -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.

+ 1 - 1
docs/sources/installation/docker.md

@@ -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

+ 2 - 0
docs/sources/project/building_from_source.md

@@ -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
 ```

+ 1 - 2
docs/sources/reference/annotations.md

@@ -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.
 

+ 18 - 0
docs/sources/reference/dashlist.md

@@ -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
+
+
 

+ 4 - 4
docs/sources/reference/http_api.md

@@ -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
 

+ 35 - 6
docs/sources/reference/templating.md

@@ -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

+ 1 - 0
docs/sources/versions.html_fragment

@@ -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>

+ 6 - 1
main.go

@@ -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()
 	}

+ 1 - 1
package.json

@@ -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"
   }
 }

+ 2 - 2
packaging/deb/control/postinst

@@ -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

+ 7 - 7
packaging/deb/init.d/grafana-server

@@ -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

+ 2 - 2
packaging/rpm/control/postinst

@@ -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

+ 15 - 7
packaging/rpm/init.d/grafana-server

@@ -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

+ 9 - 1
pkg/api/api.go

@@ -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)

+ 2 - 2
pkg/api/common.go

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

+ 3 - 2
pkg/api/dashboard.go

@@ -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)

+ 1 - 1
pkg/api/dashboard_snapshot.go

@@ -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
 	}
 

+ 11 - 1
pkg/api/dtos/user.go

@@ -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"`
+}

+ 1 - 1
pkg/api/frontendsettings.go

@@ -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,

+ 2 - 2
pkg/api/ldapauth/ldapauth.go

@@ -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
 	}

+ 9 - 31
pkg/api/login.go

@@ -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,29 +88,21 @@ 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)
 		}
-	}
 
-	if err != nil {
-		return ApiError(401, "Invalid username or password", err)
+		return ApiError(500, "Error while trying to authenticate user", err)
 	}
 
+	user := authQuery.User
+
 	loginUserWithUser(user, c)
 
 	result := map[string]interface{}{
@@ -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")

+ 6 - 2
pkg/api/org.go

@@ -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 - 0
pkg/api/password.go

@@ -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")
+}

+ 1 - 1
pkg/api/search.go

@@ -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) {

+ 13 - 7
pkg/api/signup.go

@@ -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
 
-	loginUserWithUser(&user, c)
+	bus.Publish(&events.UserSignedUp{
+		Id:    user.Id,
+		Name:  user.Name,
+		Email: user.Email,
+		Login: user.Login,
+	})
 
-	c.JsonOK("User created and logged in")
+	loginUserWithUser(&user, c)
 
 	metrics.M_Api_User_SignUp.Inc(1)
+
+	return ApiSuccess("User created and logged in")
 }

+ 27 - 16
pkg/auth/auth.go

@@ -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
+	}
 
-type GrafanaDBAuthSource struct {
+	if setting.LdapEnabled {
+		err = loginUsingLdap(query)
+	}
+
+	return err
 }
 
-func (s *GrafanaDBAuthSource) AuthenticateUser(username, password string) (*m.User, error) {
-	userQuery := m.GetUserByLoginQuery{LoginOrEmail: username}
-	err := bus.Dispatch(&userQuery)
+func loginUsingGrafanaDB(query *AuthenticateUserQuery) error {
+	userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username}
 
-	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 - 0
pkg/auth/ldap.go

@@ -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
+}

+ 2 - 2
pkg/bus/bus.go

@@ -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)

+ 1 - 0
pkg/cmd/web.go

@@ -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"),

+ 0 - 27
pkg/components/ldap/LICENSE

@@ -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.

+ 0 - 33
pkg/components/ldap/README

@@ -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

+ 0 - 63
pkg/components/ldap/_examples/enterprise.ldif

@@ -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

+ 0 - 89
pkg/components/ldap/_examples/modify.go

@@ -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)
-}

+ 0 - 52
pkg/components/ldap/_examples/search.go

@@ -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)
-}

+ 0 - 45
pkg/components/ldap/_examples/searchSSL.go

@@ -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)
-}

+ 0 - 45
pkg/components/ldap/_examples/searchTLS.go

@@ -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)
-}

+ 0 - 67
pkg/components/ldap/_examples/slapd.conf

@@ -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

+ 0 - 55
pkg/components/ldap/bind.go

@@ -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
-}

+ 0 - 275
pkg/components/ldap/conn.go

@@ -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
-		}
-
-	}
-}

+ 0 - 157
pkg/components/ldap/control.go

@@ -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
-}

+ 0 - 24
pkg/components/ldap/debug.go

@@ -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)
-	}
-}

+ 0 - 248
pkg/components/ldap/filter.go

@@ -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
-	}
-}

+ 0 - 78
pkg/components/ldap/filter_test.go

@@ -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])
-	}
-}

+ 0 - 302
pkg/components/ldap/ldap.go

@@ -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"
-}

+ 0 - 123
pkg/components/ldap/ldap_test.go

@@ -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))
-		}
-	}
-}

+ 0 - 156
pkg/components/ldap/modify.go

@@ -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
-}

+ 0 - 350
pkg/components/ldap/search.go

@@ -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
-}

+ 1 - 1
pkg/components/renderer/renderer.go

@@ -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)
 		}

+ 9 - 1
pkg/events/events.go

@@ -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"`

+ 2 - 0
pkg/middleware/auth_proxy.go

@@ -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")
 	}

+ 45 - 2
pkg/middleware/middleware.go

@@ -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:
+		metrics.M_Api_Status_404.Inc(1)
 		resp["message"] = "Not Found"
-		metrics.M_Api_Status_500.Inc(1)
 	case 500:
-		metrics.M_Api_Status_404.Inc(1)
+		metrics.M_Api_Status_500.Inc(1)
 		resp["message"] = "Internal Server Error"
 	}
 

+ 36 - 0
pkg/middleware/middleware_test.go

@@ -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" {

+ 3 - 2
pkg/models/dashboards.go

@@ -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{}
 	}
 

+ 13 - 0
pkg/models/dashboard_test.go → pkg/models/dashboards_test.go

@@ -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 - 0
pkg/models/emails.go

@@ -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
+}

+ 0 - 4
pkg/models/models.go

@@ -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")

+ 10 - 0
pkg/models/user.go

@@ -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 changed in this diff