Started work on LDAP again, #1450

This commit is contained in:
Torkel Ödegaard
2015-07-10 11:10:48 +02:00
376 changed files with 32968 additions and 8624 deletions
+9 -1
View File
@@ -26,6 +26,7 @@ func Register(r *macaron.Macaron) {
// authed views
r.Get("/profile/", reqSignedIn, Index)
r.Get("/org/", reqSignedIn, Index)
r.Get("/org/new", reqSignedIn, Index)
r.Get("/datasources/", reqSignedIn, Index)
r.Get("/datasources/edit/*", reqSignedIn, Index)
r.Get("/org/users/", reqSignedIn, Index)
@@ -39,7 +40,14 @@ func Register(r *macaron.Macaron) {
// sign up
r.Get("/signup", Index)
r.Post("/api/user/signup", bind(m.CreateUserCommand{}), SignUp)
r.Post("/api/user/signup", bind(m.CreateUserCommand{}), wrap(SignUp))
// reset password
r.Get("/user/password/send-reset-email", Index)
r.Get("/user/password/reset", Index)
r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))
// dashboard snapshots
r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
+3 -3
View File
@@ -87,10 +87,10 @@ func ApiError(status int, message string, err error) *NormalResponse {
switch status {
case 404:
resp["message"] = "Not Found"
metrics.M_Api_Status_500.Inc(1)
case 500:
metrics.M_Api_Status_404.Inc(1)
resp["message"] = "Not Found"
case 500:
metrics.M_Api_Status_500.Inc(1)
resp["message"] = "Internal Server Error"
}
+3 -2
View File
@@ -4,13 +4,14 @@ import (
"encoding/json"
"os"
"path"
"strings"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/search"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@@ -31,7 +32,7 @@ func isDasboardStarredByUser(c *middleware.Context, dashId int64) (bool, error)
func GetDashboard(c *middleware.Context) {
metrics.M_Api_Dashboard_Get.Inc(1)
slug := c.Params(":slug")
slug := strings.ToLower(c.Params(":slug"))
query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
err := bus.Dispatch(&query)
+1 -1
View File
@@ -59,7 +59,7 @@ func GetDashboardSnapshot(c *middleware.Context) {
// expired snapshots should also be removed from db
if snapshot.Expires.Before(time.Now()) {
c.JsonApiErr(404, "Snapshot not found", err)
c.JsonApiErr(404, "Dashboard snapshot not found", err)
return
}
+11 -1
View File
@@ -18,7 +18,7 @@ type AdminUpdateUserPasswordForm struct {
}
type AdminUpdateUserPermissionsForm struct {
IsGrafanaAdmin bool `json:"IsGrafanaAdmin" binding:"Required"`
IsGrafanaAdmin bool `json:"IsGrafanaAdmin"`
}
type AdminUserListItem struct {
@@ -27,3 +27,13 @@ type AdminUserListItem struct {
Login string `json:"login"`
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
}
type SendResetPasswordEmailForm struct {
UserOrEmail string `json:"userOrEmail" binding:"Required"`
}
type ResetUserPasswordForm struct {
Code string `json:"code"`
NewPassword string `json:"newPassword"`
ConfirmPassword string `json:"confirmPassword"`
}
+1 -1
View File
@@ -99,7 +99,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
"defaultDatasource": defaultDatasource,
"datasources": datasources,
"appSubUrl": setting.AppSubUrl,
"viewerRoleMode": setting.ViewerRoleMode,
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"buildInfo": map[string]interface{}{
"version": setting.BuildVersion,
"commit": setting.BuildCommit,
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/gogits/gogs/modules/ldap"
"github.com/go-ldap/ldap"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
)
@@ -15,7 +15,7 @@ var (
)
func Login(username, password string) error {
url, err := url.Parse(setting.LdapUrls[0])
url, err := url.Parse(setting.LdapHosts[0])
if err != nil {
return err
}
+9 -31
View File
@@ -4,7 +4,6 @@ import (
"net/url"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/ldapauth"
"github.com/grafana/grafana/pkg/auth"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
@@ -89,28 +88,20 @@ func LoginApiPing(c *middleware.Context) {
}
func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response {
sourcesQuery := auth.GetAuthSourcesQuery{}
if err := bus.Dispatch(&sourcesQuery); err != nil {
return ApiError(500, "Could not get login sources", err)
authQuery := auth.AuthenticateUserQuery{
Username: cmd.User,
Password: cmd.Password,
}
var err error
var user *m.User
for _, authSource := range sourcesQuery.Sources {
user, err = authSource.AuthenticateUser(cmd.User, cmd.Password)
if err == nil {
break
}
// handle non invalid credentials error, otherwise try next auth source
if err != auth.ErrInvalidCredentials {
return ApiError(500, "Error while trying to authenticate user", err)
if err := bus.Dispatch(&authQuery); err != nil {
if err == auth.ErrInvalidCredentials {
return ApiError(401, "Invalid username or password", err)
}
return ApiError(500, "Error while trying to authenticate user", err)
}
if err != nil {
return ApiError(401, "Invalid username or password", err)
}
user := authQuery.User
loginUserWithUser(user, c)
@@ -128,19 +119,6 @@ func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response {
return Json(200, result)
}
func LoginUsingLdap(c *middleware.Context, cmd dtos.LoginCommand) Response {
err := ldapauth.Login(cmd.User, cmd.Password)
if err != nil {
if err == ldapauth.ErrInvalidCredentials {
return ApiError(401, "Invalid username or password", err)
}
return ApiError(500, "Ldap login failed", err)
}
return Empty(401)
}
func loginUserWithUser(user *m.User, c *middleware.Context) {
if user == nil {
log.Error(3, "User login with nil user")
+6 -2
View File
@@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
// GET /api/org
@@ -39,7 +40,7 @@ func getOrgHelper(orgId int64) Response {
// POST /api/orgs
func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) Response {
if !setting.AllowUserOrgCreate && !c.IsGrafanaAdmin {
if !c.IsSignedIn || (!setting.AllowUserOrgCreate && !c.IsGrafanaAdmin) {
return ApiError(401, "Access denied", nil)
}
@@ -50,7 +51,10 @@ func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) Response {
metrics.M_Api_Org_Create.Inc(1)
return ApiSuccess("Organization created")
return Json(200, &util.DynMap{
"orgId": cmd.Result.Id,
"message": "Organization created",
})
}
// PUT /api/org
+49
View File
@@ -0,0 +1,49 @@
package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util"
)
func SendResetPasswordEmail(c *middleware.Context, form dtos.SendResetPasswordEmailForm) Response {
userQuery := m.GetUserByLoginQuery{LoginOrEmail: form.UserOrEmail}
if err := bus.Dispatch(&userQuery); err != nil {
return ApiError(404, "User does not exist", err)
}
emailCmd := m.SendResetPasswordEmailCommand{User: userQuery.Result}
if err := bus.Dispatch(&emailCmd); err != nil {
return ApiError(500, "Failed to send email", err)
}
return ApiSuccess("Email sent")
}
func ResetPassword(c *middleware.Context, form dtos.ResetUserPasswordForm) Response {
query := m.ValidateResetPasswordCodeQuery{Code: form.Code}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrInvalidEmailCode {
return ApiError(400, "Invalid or expired reset password code", nil)
}
return ApiError(500, "Unknown error validating email code", err)
}
if form.NewPassword != form.ConfirmPassword {
return ApiError(400, "Passwords do not match", nil)
}
cmd := m.ChangeUserPasswordCommand{}
cmd.UserId = query.Result.Id
cmd.NewPassword = util.EncodePassword(form.NewPassword, query.Result.Salt)
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to change user password", err)
}
return ApiSuccess("User password changed")
}
+1 -1
View File
@@ -3,7 +3,7 @@ package api
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/search"
"github.com/grafana/grafana/pkg/services/search"
)
func Search(c *middleware.Context) {
+13 -7
View File
@@ -2,6 +2,7 @@ package api
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
@@ -9,24 +10,29 @@ import (
)
// POST /api/user/signup
func SignUp(c *middleware.Context, cmd m.CreateUserCommand) {
func SignUp(c *middleware.Context, cmd m.CreateUserCommand) Response {
if !setting.AllowUserSignUp {
c.JsonApiErr(401, "User signup is disabled", nil)
return
return ApiError(401, "User signup is disabled", nil)
}
cmd.Login = cmd.Email
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "failed to create user", err)
return
return ApiError(500, "failed to create user", err)
}
user := cmd.Result
bus.Publish(&events.UserSignedUp{
Id: user.Id,
Name: user.Name,
Email: user.Email,
Login: user.Login,
})
loginUserWithUser(&user, c)
c.JsonOK("User created and logged in")
metrics.M_Api_User_SignUp.Inc(1)
return ApiSuccess("User created and logged in")
}
+28 -17
View File
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@@ -38,36 +39,46 @@ type AuthSource interface {
AuthenticateUser(username, password string) (*m.User, error)
}
type GetAuthSourcesQuery struct {
Sources []AuthSource
type AuthenticateUserQuery struct {
Username string
Password string
User *m.User
}
func init() {
bus.AddHandler("auth", GetAuthSources)
bus.AddHandler("auth", AuthenticateUser)
}
func GetAuthSources(query *GetAuthSourcesQuery) error {
query.Sources = []AuthSource{&GrafanaDBAuthSource{}}
return nil
func AuthenticateUser(query *AuthenticateUserQuery) error {
err := loginUsingGrafanaDB(query)
if err == nil || err != ErrInvalidCredentials {
return err
}
if setting.LdapEnabled {
err = loginUsingLdap(query)
}
return err
}
type GrafanaDBAuthSource struct {
}
func loginUsingGrafanaDB(query *AuthenticateUserQuery) error {
userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username}
func (s *GrafanaDBAuthSource) AuthenticateUser(username, password string) (*m.User, error) {
userQuery := m.GetUserByLoginQuery{LoginOrEmail: username}
err := bus.Dispatch(&userQuery)
if err != nil {
return nil, ErrInvalidCredentials
if err := bus.Dispatch(&userQuery); err != nil {
if err == m.ErrUserNotFound {
return ErrInvalidCredentials
}
return err
}
user := userQuery.Result
passwordHashed := util.EncodePassword(password, user.Salt)
passwordHashed := util.EncodePassword(query.Password, user.Salt)
if passwordHashed != user.Password {
return nil, ErrInvalidCredentials
return ErrInvalidCredentials
}
return user, nil
query.User = user
return nil
}
+55
View File
@@ -0,0 +1,55 @@
package auth
import (
"fmt"
"net/url"
"github.com/go-ldap/ldap"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
func loginUsingLdap(query *AuthenticateUserQuery) error {
url, err := url.Parse(setting.LdapHosts[0])
if err != nil {
return err
}
log.Info("Host: %v", url.Host)
conn, err := ldap.Dial("tcp", url.Host)
if err != nil {
return err
}
defer conn.Close()
bindFormat := "cn=%s,dc=grafana,dc=org"
nx := fmt.Sprintf(bindFormat, query.Username)
err = conn.Bind(nx, query.Password)
if err != nil {
if ldapErr, ok := err.(*ldap.Error); ok {
if ldapErr.ResultCode == 49 {
return ErrInvalidCredentials
}
}
return err
}
userQuery := m.GetUserByLoginQuery{LoginOrEmail: "admin"}
err = bus.Dispatch(&userQuery)
if err != nil {
if err == m.ErrUserNotFound {
return ErrInvalidCredentials
}
return err
}
query.User = userQuery.Result
return nil
}
+2 -2
View File
@@ -1,7 +1,7 @@
package bus
import (
"errors"
"fmt"
"reflect"
)
@@ -39,7 +39,7 @@ func (b *InProcBus) Dispatch(msg Msg) error {
var handler = b.handlers[msgName]
if handler == nil {
return errors.New("handler not found")
return fmt.Errorf("handler not found for %s", msgName)
}
var params = make([]reflect.Value, 1)
+1
View File
@@ -33,6 +33,7 @@ func newMacaron() *macaron.Macaron {
mapStatic(m, "css", "css")
mapStatic(m, "img", "img")
mapStatic(m, "fonts", "fonts")
mapStatic(m, "robots.txt", "robots.txxt")
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: path.Join(setting.StaticRootPath, "views"),
-27
View File
@@ -1,27 +0,0 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-33
View File
@@ -1,33 +0,0 @@
Basic LDAP v3 functionality for the GO programming language.
Required Librarys:
github.com/johnweldon/asn1-ber
Working:
Connecting to LDAP server
Binding to LDAP server
Searching for entries
Compiling string filters to LDAP filters
Paging Search Results
Modify Requests / Responses
Examples:
search
modify
Tests Implemented:
Filter Compile / Decompile
TODO:
Add Requests / Responses
Delete Requests / Responses
Modify DN Requests / Responses
Compare Requests / Responses
Implement Tests / Benchmarks
This feature is disabled at the moment, because in some cases the "Search Request Done" packet will be handled before the last "Search Request Entry":
Mulitple internal goroutines to handle network traffic
Makes library goroutine safe
Can perform multiple search requests at the same time and return
the results to the proper goroutine. All requests are blocking
requests, so the goroutine does not need special handling
@@ -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
-89
View File
@@ -1,89 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"log"
"github.com/gogits/gogs/modules/ldap"
)
var (
LdapServer string = "localhost"
LdapPort uint16 = 389
BaseDN string = "dc=enterprise,dc=org"
BindDN string = "cn=admin,dc=enterprise,dc=org"
BindPW string = "enterprise"
Filter string = "(cn=kirkj)"
)
func search(l *ldap.Conn, filter string, attributes []string) (*ldap.Entry, *ldap.Error) {
search := ldap.NewSearchRequest(
BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
attributes,
nil)
sr, err := l.Search(search)
if err != nil {
log.Fatalf("ERROR: %s\n", err)
return nil, err
}
log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries))
if len(sr.Entries) == 0 {
return nil, ldap.NewError(ldap.ErrorDebugging, errors.New(fmt.Sprintf("no entries found for: %s", filter)))
}
return sr.Entries[0], nil
}
func main() {
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", LdapServer, LdapPort))
if err != nil {
log.Fatalf("ERROR: %s\n", err.Error())
}
defer l.Close()
// l.Debug = true
l.Bind(BindDN, BindPW)
log.Printf("The Search for Kirk ... %s\n", Filter)
entry, err := search(l, Filter, []string{})
if err != nil {
log.Fatal("could not get entry")
}
entry.PrettyPrint(0)
log.Printf("modify the mail address and add a description ... \n")
modify := ldap.NewModifyRequest(entry.DN)
modify.Add("description", []string{"Captain of the USS Enterprise"})
modify.Replace("mail", []string{"captain@enterprise.org"})
if err := l.Modify(modify); err != nil {
log.Fatalf("ERROR: %s\n", err.Error())
}
entry, err = search(l, Filter, []string{})
if err != nil {
log.Fatal("could not get entry")
}
entry.PrettyPrint(0)
log.Printf("reset the entry ... \n")
modify = ldap.NewModifyRequest(entry.DN)
modify.Delete("description", []string{})
modify.Replace("mail", []string{"james.kirk@enterprise.org"})
if err := l.Modify(modify); err != nil {
log.Fatalf("ERROR: %s\n", err.Error())
}
entry, err = search(l, Filter, []string{})
if err != nil {
log.Fatal("could not get entry")
}
entry.PrettyPrint(0)
}
-52
View File
@@ -1,52 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"log"
"github.com/gogits/gogs/modules/ldap"
)
var (
ldapServer string = "adserver"
ldapPort uint16 = 3268
baseDN string = "dc=*,dc=*"
filter string = "(&(objectClass=user)(sAMAccountName=*)(memberOf=CN=*,OU=*,DC=*,DC=*))"
Attributes []string = []string{"memberof"}
user string = "*"
passwd string = "*"
)
func main() {
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
if err != nil {
log.Fatalf("ERROR: %s\n", err.Error())
}
defer l.Close()
// l.Debug = true
err = l.Bind(user, passwd)
if err != nil {
log.Printf("ERROR: Cannot bind: %s\n", err.Error())
return
}
search := ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
Attributes,
nil)
sr, err := l.Search(search)
if err != nil {
log.Fatalf("ERROR: %s\n", err.Error())
return
}
log.Printf("Search: %s -> num of entries = %d\n", search.Filter, len(sr.Entries))
sr.PrettyPrint(0)
}
@@ -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)
}
@@ -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)
}
-67
View File
@@ -1,67 +0,0 @@
#
# See slapd.conf(5) for details on configuration options.
# This file should NOT be world readable.
#
include /private/etc/openldap/schema/core.schema
include /private/etc/openldap/schema/cosine.schema
include /private/etc/openldap/schema/inetorgperson.schema
# Define global ACLs to disable default read access.
# Do not enable referrals until AFTER you have a working directory
# service AND an understanding of referrals.
#referral ldap://root.openldap.org
pidfile /private/var/db/openldap/run/slapd.pid
argsfile /private/var/db/openldap/run/slapd.args
# Load dynamic backend modules:
# modulepath /usr/libexec/openldap
# moduleload back_bdb.la
# moduleload back_hdb.la
# moduleload back_ldap.la
# Sample security restrictions
# Require integrity protection (prevent hijacking)
# Require 112-bit (3DES or better) encryption for updates
# Require 63-bit encryption for simple bind
# security ssf=1 update_ssf=112 simple_bind=64
# Sample access control policy:
# Root DSE: allow anyone to read it
# Subschema (sub)entry DSE: allow anyone to read it
# Other DSEs:
# Allow self write access
# Allow authenticated users read access
# Allow anonymous users to authenticate
# Directives needed to implement policy:
# access to dn.base="" by * read
# access to dn.base="cn=Subschema" by * read
# access to *
# by self write
# by users read
# by anonymous auth
#
# if no access controls are present, the default policy
# allows anyone and everyone to read anything but restricts
# updates to rootdn. (e.g., "access to * by * read")
#
# rootdn can always read and write EVERYTHING!
#######################################################################
# BDB database definitions
#######################################################################
database bdb
suffix "dc=enterprise,dc=org"
rootdn "cn=admin,dc=enterprise,dc=org"
# Cleartext passwords, especially for the rootdn, should
# be avoid. See slappasswd(8) and slapd.conf(5) for details.
# Use of strong authentication encouraged.
rootpw {SSHA}laO00HsgszhK1O0Z5qR0/i/US69Osfeu
# The database directory MUST exist prior to running slapd AND
# should only be accessible by the slapd and slap tools.
# Mode 700 recommended.
directory /private/var/db/openldap/openldap-data
# Indices to maintain
index objectClass eq
-55
View File
@@ -1,55 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ldap
import (
"errors"
"github.com/gogits/gogs/modules/asn1-ber"
)
func (l *Conn) Bind(username, password string) error {
messageID := l.nextMessageID()
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, username, "User Name"))
bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, password, "Password"))
packet.AppendChild(bindRequest)
if l.Debug {
ber.PrintPacket(packet)
}
channel, err := l.sendMessage(packet)
if err != nil {
return err
}
if channel == nil {
return NewError(ErrorNetwork, errors.New("ldap: could not send message"))
}
defer l.finishMessage(messageID)
packet = <-channel
if packet == nil {
return NewError(ErrorNetwork, errors.New("ldap: could not retrieve response"))
}
if l.Debug {
if err := addLDAPDescriptions(packet); err != nil {
return err
}
ber.PrintPacket(packet)
}
resultCode, resultDescription := getLDAPResultCode(packet)
if resultCode != 0 {
return NewError(resultCode, errors.New(resultDescription))
}
return nil
}
-275
View File
@@ -1,275 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ldap
import (
"crypto/tls"
"errors"
"log"
"net"
"sync"
"github.com/gogits/gogs/modules/asn1-ber"
)
const (
MessageQuit = 0
MessageRequest = 1
MessageResponse = 2
MessageFinish = 3
)
type messagePacket struct {
Op int
MessageID uint64
Packet *ber.Packet
Channel chan *ber.Packet
}
// Conn represents an LDAP Connection
type Conn struct {
conn net.Conn
isTLS bool
isClosing bool
Debug debugging
chanConfirm chan bool
chanResults map[uint64]chan *ber.Packet
chanMessage chan *messagePacket
chanMessageID chan uint64
wgSender sync.WaitGroup
wgClose sync.WaitGroup
once sync.Once
}
// Dial connects to the given address on the given network using net.Dial
// and then returns a new Conn for the connection.
func Dial(network, addr string) (*Conn, error) {
c, err := net.Dial(network, addr)
if err != nil {
return nil, NewError(ErrorNetwork, err)
}
conn := NewConn(c)
conn.start()
return conn, nil
}
// DialTLS connects to the given address on the given network using tls.Dial
// and then returns a new Conn for the connection.
func DialTLS(network, addr string, config *tls.Config) (*Conn, error) {
c, err := tls.Dial(network, addr, config)
if err != nil {
return nil, NewError(ErrorNetwork, err)
}
conn := NewConn(c)
conn.isTLS = true
conn.start()
return conn, nil
}
// NewConn returns a new Conn using conn for network I/O.
func NewConn(conn net.Conn) *Conn {
return &Conn{
conn: conn,
chanConfirm: make(chan bool),
chanMessageID: make(chan uint64),
chanMessage: make(chan *messagePacket, 10),
chanResults: map[uint64]chan *ber.Packet{},
}
}
func (l *Conn) start() {
go l.reader()
go l.processMessages()
l.wgClose.Add(1)
}
// Close closes the connection.
func (l *Conn) Close() {
l.once.Do(func() {
l.isClosing = true
l.wgSender.Wait()
l.Debug.Printf("Sending quit message and waiting for confirmation")
l.chanMessage <- &messagePacket{Op: MessageQuit}
<-l.chanConfirm
close(l.chanMessage)
l.Debug.Printf("Closing network connection")
if err := l.conn.Close(); err != nil {
log.Print(err)
}
l.conn = nil
l.wgClose.Done()
})
l.wgClose.Wait()
}
// Returns the next available messageID
func (l *Conn) nextMessageID() uint64 {
if l.chanMessageID != nil {
if messageID, ok := <-l.chanMessageID; ok {
return messageID
}
}
return 0
}
// StartTLS sends the command to start a TLS session and then creates a new TLS Client
func (l *Conn) StartTLS(config *tls.Config) error {
messageID := l.nextMessageID()
if l.isTLS {
return NewError(ErrorNetwork, errors.New("ldap: already encrypted"))
}
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS")
request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command"))
packet.AppendChild(request)
l.Debug.PrintPacket(packet)
_, err := l.conn.Write(packet.Bytes())
if err != nil {
return NewError(ErrorNetwork, err)
}
packet, err = ber.ReadPacket(l.conn)
if err != nil {
return NewError(ErrorNetwork, err)
}
if l.Debug {
if err := addLDAPDescriptions(packet); err != nil {
return err
}
ber.PrintPacket(packet)
}
if packet.Children[1].Children[0].Value.(uint64) == 0 {
conn := tls.Client(l.conn, config)
l.isTLS = true
l.conn = conn
}
return nil
}
func (l *Conn) sendMessage(packet *ber.Packet) (chan *ber.Packet, error) {
if l.isClosing {
return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed"))
}
out := make(chan *ber.Packet)
message := &messagePacket{
Op: MessageRequest,
MessageID: packet.Children[0].Value.(uint64),
Packet: packet,
Channel: out,
}
l.sendProcessMessage(message)
return out, nil
}
func (l *Conn) finishMessage(messageID uint64) {
if l.isClosing {
return
}
message := &messagePacket{
Op: MessageFinish,
MessageID: messageID,
}
l.sendProcessMessage(message)
}
func (l *Conn) sendProcessMessage(message *messagePacket) bool {
if l.isClosing {
return false
}
l.wgSender.Add(1)
l.chanMessage <- message
l.wgSender.Done()
return true
}
func (l *Conn) processMessages() {
defer func() {
for messageID, channel := range l.chanResults {
l.Debug.Printf("Closing channel for MessageID %d", messageID)
close(channel)
delete(l.chanResults, messageID)
}
close(l.chanMessageID)
l.chanConfirm <- true
close(l.chanConfirm)
}()
var messageID uint64 = 1
for {
select {
case l.chanMessageID <- messageID:
messageID++
case messagePacket, ok := <-l.chanMessage:
if !ok {
l.Debug.Printf("Shutting down - message channel is closed")
return
}
switch messagePacket.Op {
case MessageQuit:
l.Debug.Printf("Shutting down - quit message received")
return
case MessageRequest:
// Add to message list and write to network
l.Debug.Printf("Sending message %d", messagePacket.MessageID)
l.chanResults[messagePacket.MessageID] = messagePacket.Channel
// go routine
buf := messagePacket.Packet.Bytes()
_, err := l.conn.Write(buf)
if err != nil {
l.Debug.Printf("Error Sending Message: %s", err.Error())
break
}
case MessageResponse:
l.Debug.Printf("Receiving message %d", messagePacket.MessageID)
if chanResult, ok := l.chanResults[messagePacket.MessageID]; ok {
chanResult <- messagePacket.Packet
} else {
log.Printf("Received unexpected message %d", messagePacket.MessageID)
ber.PrintPacket(messagePacket.Packet)
}
case MessageFinish:
// Remove from message list
l.Debug.Printf("Finished message %d", messagePacket.MessageID)
close(l.chanResults[messagePacket.MessageID])
delete(l.chanResults, messagePacket.MessageID)
}
}
}
}
func (l *Conn) reader() {
defer func() {
l.Close()
}()
for {
packet, err := ber.ReadPacket(l.conn)
if err != nil {
l.Debug.Printf("reader: %s", err.Error())
return
}
addLDAPDescriptions(packet)
message := &messagePacket{
Op: MessageResponse,
MessageID: packet.Children[0].Value.(uint64),
Packet: packet,
}
if !l.sendProcessMessage(message) {
return
}
}
}
-157
View File
@@ -1,157 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ldap
import (
"fmt"
"github.com/gogits/gogs/modules/asn1-ber"
)
const (
ControlTypePaging = "1.2.840.113556.1.4.319"
)
var ControlTypeMap = map[string]string{
ControlTypePaging: "Paging",
}
type Control interface {
GetControlType() string
Encode() *ber.Packet
String() string
}
type ControlString struct {
ControlType string
Criticality bool
ControlValue string
}
func (c *ControlString) GetControlType() string {
return c.ControlType
}
func (c *ControlString) Encode() *ber.Packet {
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlType, "Control Type ("+ControlTypeMap[c.ControlType]+")"))
if c.Criticality {
packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
}
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlValue, "Control Value"))
return packet
}
func (c *ControlString) String() string {
return fmt.Sprintf("Control Type: %s (%q) Criticality: %t Control Value: %s", ControlTypeMap[c.ControlType], c.ControlType, c.Criticality, c.ControlValue)
}
type ControlPaging struct {
PagingSize uint32
Cookie []byte
}
func (c *ControlPaging) GetControlType() string {
return ControlTypePaging
}
func (c *ControlPaging) Encode() *ber.Packet {
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypePaging, "Control Type ("+ControlTypeMap[ControlTypePaging]+")"))
p2 := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Paging)")
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Search Control Value")
seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(c.PagingSize), "Paging Size"))
cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie")
cookie.Value = c.Cookie
cookie.Data.Write(c.Cookie)
seq.AppendChild(cookie)
p2.AppendChild(seq)
packet.AppendChild(p2)
return packet
}
func (c *ControlPaging) String() string {
return fmt.Sprintf(
"Control Type: %s (%q) Criticality: %t PagingSize: %d Cookie: %q",
ControlTypeMap[ControlTypePaging],
ControlTypePaging,
false,
c.PagingSize,
c.Cookie)
}
func (c *ControlPaging) SetCookie(cookie []byte) {
c.Cookie = cookie
}
func FindControl(controls []Control, controlType string) Control {
for _, c := range controls {
if c.GetControlType() == controlType {
return c
}
}
return nil
}
func DecodeControl(packet *ber.Packet) Control {
ControlType := packet.Children[0].Value.(string)
Criticality := false
packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
value := packet.Children[1]
if len(packet.Children) == 3 {
value = packet.Children[2]
packet.Children[1].Description = "Criticality"
Criticality = packet.Children[1].Value.(bool)
}
value.Description = "Control Value"
switch ControlType {
case ControlTypePaging:
value.Description += " (Paging)"
c := new(ControlPaging)
if value.Value != nil {
valueChildren := ber.DecodePacket(value.Data.Bytes())
value.Data.Truncate(0)
value.Value = nil
value.AppendChild(valueChildren)
}
value = value.Children[0]
value.Description = "Search Control Value"
value.Children[0].Description = "Paging Size"
value.Children[1].Description = "Cookie"
c.PagingSize = uint32(value.Children[0].Value.(uint64))
c.Cookie = value.Children[1].Data.Bytes()
value.Children[1].Value = c.Cookie
return c
}
c := new(ControlString)
c.ControlType = ControlType
c.Criticality = Criticality
c.ControlValue = value.Value.(string)
return c
}
func NewControlString(controlType string, criticality bool, controlValue string) *ControlString {
return &ControlString{
ControlType: controlType,
Criticality: criticality,
ControlValue: controlValue,
}
}
func NewControlPaging(pagingSize uint32) *ControlPaging {
return &ControlPaging{PagingSize: pagingSize}
}
func encodeControls(controls []Control) *ber.Packet {
packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls")
for _, control := range controls {
packet.AppendChild(control.Encode())
}
return packet
}
-24
View File
@@ -1,24 +0,0 @@
package ldap
import (
"log"
"github.com/gogits/gogs/modules/asn1-ber"
)
// debugging type
// - has a Printf method to write the debug output
type debugging bool
// write debug output
func (debug debugging) Printf(format string, args ...interface{}) {
if debug {
log.Printf(format, args...)
}
}
func (debug debugging) PrintPacket(packet *ber.Packet) {
if debug {
ber.PrintPacket(packet)
}
}
-248
View File
@@ -1,248 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ldap
import (
"errors"
"fmt"
"github.com/gogits/gogs/modules/asn1-ber"
)
const (
FilterAnd = 0
FilterOr = 1
FilterNot = 2
FilterEqualityMatch = 3
FilterSubstrings = 4
FilterGreaterOrEqual = 5
FilterLessOrEqual = 6
FilterPresent = 7
FilterApproxMatch = 8
FilterExtensibleMatch = 9
)
var FilterMap = map[uint64]string{
FilterAnd: "And",
FilterOr: "Or",
FilterNot: "Not",
FilterEqualityMatch: "Equality Match",
FilterSubstrings: "Substrings",
FilterGreaterOrEqual: "Greater Or Equal",
FilterLessOrEqual: "Less Or Equal",
FilterPresent: "Present",
FilterApproxMatch: "Approx Match",
FilterExtensibleMatch: "Extensible Match",
}
const (
FilterSubstringsInitial = 0
FilterSubstringsAny = 1
FilterSubstringsFinal = 2
)
var FilterSubstringsMap = map[uint64]string{
FilterSubstringsInitial: "Substrings Initial",
FilterSubstringsAny: "Substrings Any",
FilterSubstringsFinal: "Substrings Final",
}
func CompileFilter(filter string) (*ber.Packet, error) {
if len(filter) == 0 || filter[0] != '(' {
return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('"))
}
packet, pos, err := compileFilter(filter, 1)
if err != nil {
return nil, err
}
if pos != len(filter) {
return nil, NewError(ErrorFilterCompile, errors.New("ldap: finished compiling filter with extra at end: "+fmt.Sprint(filter[pos:])))
}
return packet, nil
}
func DecompileFilter(packet *ber.Packet) (ret string, err error) {
defer func() {
if r := recover(); r != nil {
err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter"))
}
}()
ret = "("
err = nil
childStr := ""
switch packet.Tag {
case FilterAnd:
ret += "&"
for _, child := range packet.Children {
childStr, err = DecompileFilter(child)
if err != nil {
return
}
ret += childStr
}
case FilterOr:
ret += "|"
for _, child := range packet.Children {
childStr, err = DecompileFilter(child)
if err != nil {
return
}
ret += childStr
}
case FilterNot:
ret += "!"
childStr, err = DecompileFilter(packet.Children[0])
if err != nil {
return
}
ret += childStr
case FilterSubstrings:
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "="
switch packet.Children[1].Children[0].Tag {
case FilterSubstringsInitial:
ret += ber.DecodeString(packet.Children[1].Children[0].Data.Bytes()) + "*"
case FilterSubstringsAny:
ret += "*" + ber.DecodeString(packet.Children[1].Children[0].Data.Bytes()) + "*"
case FilterSubstringsFinal:
ret += "*" + ber.DecodeString(packet.Children[1].Children[0].Data.Bytes())
}
case FilterEqualityMatch:
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "="
ret += ber.DecodeString(packet.Children[1].Data.Bytes())
case FilterGreaterOrEqual:
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += ">="
ret += ber.DecodeString(packet.Children[1].Data.Bytes())
case FilterLessOrEqual:
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "<="
ret += ber.DecodeString(packet.Children[1].Data.Bytes())
case FilterPresent:
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "=*"
case FilterApproxMatch:
ret += ber.DecodeString(packet.Children[0].Data.Bytes())
ret += "~="
ret += ber.DecodeString(packet.Children[1].Data.Bytes())
}
ret += ")"
return
}
func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) {
for pos < len(filter) && filter[pos] == '(' {
child, newPos, err := compileFilter(filter, pos+1)
if err != nil {
return pos, err
}
pos = newPos
parent.AppendChild(child)
}
if pos == len(filter) {
return pos, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
}
return pos + 1, nil
}
func compileFilter(filter string, pos int) (*ber.Packet, int, error) {
var packet *ber.Packet
var err error
defer func() {
if r := recover(); r != nil {
err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter"))
}
}()
newPos := pos
switch filter[pos] {
case '(':
packet, newPos, err = compileFilter(filter, pos+1)
newPos++
return packet, newPos, err
case '&':
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd])
newPos, err = compileFilterSet(filter, pos+1, packet)
return packet, newPos, err
case '|':
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr])
newPos, err = compileFilterSet(filter, pos+1, packet)
return packet, newPos, err
case '!':
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot])
var child *ber.Packet
child, newPos, err = compileFilter(filter, pos+1)
packet.AppendChild(child)
return packet, newPos, err
default:
attribute := ""
condition := ""
for newPos < len(filter) && filter[newPos] != ')' {
switch {
case packet != nil:
condition += fmt.Sprintf("%c", filter[newPos])
case filter[newPos] == '=':
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch])
case filter[newPos] == '>' && filter[newPos+1] == '=':
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual])
newPos++
case filter[newPos] == '<' && filter[newPos+1] == '=':
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual])
newPos++
case filter[newPos] == '~' && filter[newPos+1] == '=':
packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterLessOrEqual])
newPos++
case packet == nil:
attribute += fmt.Sprintf("%c", filter[newPos])
}
newPos++
}
if newPos == len(filter) {
err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter"))
return packet, newPos, err
}
if packet == nil {
err = NewError(ErrorFilterCompile, errors.New("ldap: error parsing filter"))
return packet, newPos, err
}
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
switch {
case packet.Tag == FilterEqualityMatch && condition == "*":
packet.Tag = FilterPresent
packet.Description = FilterMap[uint64(packet.Tag)]
case packet.Tag == FilterEqualityMatch && condition[0] == '*' && condition[len(condition)-1] == '*':
// Any
packet.Tag = FilterSubstrings
packet.Description = FilterMap[uint64(packet.Tag)]
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterSubstringsAny, condition[1:len(condition)-1], "Any Substring"))
packet.AppendChild(seq)
case packet.Tag == FilterEqualityMatch && condition[0] == '*':
// Final
packet.Tag = FilterSubstrings
packet.Description = FilterMap[uint64(packet.Tag)]
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterSubstringsFinal, condition[1:], "Final Substring"))
packet.AppendChild(seq)
case packet.Tag == FilterEqualityMatch && condition[len(condition)-1] == '*':
// Initial
packet.Tag = FilterSubstrings
packet.Description = FilterMap[uint64(packet.Tag)]
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings")
seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterSubstringsInitial, condition[:len(condition)-1], "Initial Substring"))
packet.AppendChild(seq)
default:
packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, condition, "Condition"))
}
newPos++
return packet, newPos, err
}
}
-78
View File
@@ -1,78 +0,0 @@
package ldap
import (
"testing"
"github.com/gogits/gogs/modules/asn1-ber"
)
type compileTest struct {
filterStr string
filterType int
}
var testFilters = []compileTest{
compileTest{filterStr: "(&(sn=Miller)(givenName=Bob))", filterType: FilterAnd},
compileTest{filterStr: "(|(sn=Miller)(givenName=Bob))", filterType: FilterOr},
compileTest{filterStr: "(!(sn=Miller))", filterType: FilterNot},
compileTest{filterStr: "(sn=Miller)", filterType: FilterEqualityMatch},
compileTest{filterStr: "(sn=Mill*)", filterType: FilterSubstrings},
compileTest{filterStr: "(sn=*Mill)", filterType: FilterSubstrings},
compileTest{filterStr: "(sn=*Mill*)", filterType: FilterSubstrings},
compileTest{filterStr: "(sn>=Miller)", filterType: FilterGreaterOrEqual},
compileTest{filterStr: "(sn<=Miller)", filterType: FilterLessOrEqual},
compileTest{filterStr: "(sn=*)", filterType: FilterPresent},
compileTest{filterStr: "(sn~=Miller)", filterType: FilterApproxMatch},
// compileTest{ filterStr: "()", filterType: FilterExtensibleMatch },
}
func TestFilter(t *testing.T) {
// Test Compiler and Decompiler
for _, i := range testFilters {
filter, err := CompileFilter(i.filterStr)
if err != nil {
t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
} else if filter.Tag != uint8(i.filterType) {
t.Errorf("%q Expected %q got %q", i.filterStr, FilterMap[uint64(i.filterType)], FilterMap[uint64(filter.Tag)])
} else {
o, err := DecompileFilter(filter)
if err != nil {
t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
} else if i.filterStr != o {
t.Errorf("%q expected, got %q", i.filterStr, o)
}
}
}
}
func BenchmarkFilterCompile(b *testing.B) {
b.StopTimer()
filters := make([]string, len(testFilters))
// Test Compiler and Decompiler
for idx, i := range testFilters {
filters[idx] = i.filterStr
}
maxIdx := len(filters)
b.StartTimer()
for i := 0; i < b.N; i++ {
CompileFilter(filters[i%maxIdx])
}
}
func BenchmarkFilterDecompile(b *testing.B) {
b.StopTimer()
filters := make([]*ber.Packet, len(testFilters))
// Test Compiler and Decompiler
for idx, i := range testFilters {
filters[idx], _ = CompileFilter(i.filterStr)
}
maxIdx := len(filters)
b.StartTimer()
for i := 0; i < b.N; i++ {
DecompileFilter(filters[i%maxIdx])
}
}
-302
View File
@@ -1,302 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ldap
import (
"errors"
"fmt"
"io/ioutil"
"github.com/gogits/gogs/modules/asn1-ber"
)
// LDAP Application Codes
const (
ApplicationBindRequest = 0
ApplicationBindResponse = 1
ApplicationUnbindRequest = 2
ApplicationSearchRequest = 3
ApplicationSearchResultEntry = 4
ApplicationSearchResultDone = 5
ApplicationModifyRequest = 6
ApplicationModifyResponse = 7
ApplicationAddRequest = 8
ApplicationAddResponse = 9
ApplicationDelRequest = 10
ApplicationDelResponse = 11
ApplicationModifyDNRequest = 12
ApplicationModifyDNResponse = 13
ApplicationCompareRequest = 14
ApplicationCompareResponse = 15
ApplicationAbandonRequest = 16
ApplicationSearchResultReference = 19
ApplicationExtendedRequest = 23
ApplicationExtendedResponse = 24
)
var ApplicationMap = map[uint8]string{
ApplicationBindRequest: "Bind Request",
ApplicationBindResponse: "Bind Response",
ApplicationUnbindRequest: "Unbind Request",
ApplicationSearchRequest: "Search Request",
ApplicationSearchResultEntry: "Search Result Entry",
ApplicationSearchResultDone: "Search Result Done",
ApplicationModifyRequest: "Modify Request",
ApplicationModifyResponse: "Modify Response",
ApplicationAddRequest: "Add Request",
ApplicationAddResponse: "Add Response",
ApplicationDelRequest: "Del Request",
ApplicationDelResponse: "Del Response",
ApplicationModifyDNRequest: "Modify DN Request",
ApplicationModifyDNResponse: "Modify DN Response",
ApplicationCompareRequest: "Compare Request",
ApplicationCompareResponse: "Compare Response",
ApplicationAbandonRequest: "Abandon Request",
ApplicationSearchResultReference: "Search Result Reference",
ApplicationExtendedRequest: "Extended Request",
ApplicationExtendedResponse: "Extended Response",
}
// LDAP Result Codes
const (
LDAPResultSuccess = 0
LDAPResultOperationsError = 1
LDAPResultProtocolError = 2
LDAPResultTimeLimitExceeded = 3
LDAPResultSizeLimitExceeded = 4
LDAPResultCompareFalse = 5
LDAPResultCompareTrue = 6
LDAPResultAuthMethodNotSupported = 7
LDAPResultStrongAuthRequired = 8
LDAPResultReferral = 10
LDAPResultAdminLimitExceeded = 11
LDAPResultUnavailableCriticalExtension = 12
LDAPResultConfidentialityRequired = 13
LDAPResultSaslBindInProgress = 14
LDAPResultNoSuchAttribute = 16
LDAPResultUndefinedAttributeType = 17
LDAPResultInappropriateMatching = 18
LDAPResultConstraintViolation = 19
LDAPResultAttributeOrValueExists = 20
LDAPResultInvalidAttributeSyntax = 21
LDAPResultNoSuchObject = 32
LDAPResultAliasProblem = 33
LDAPResultInvalidDNSyntax = 34
LDAPResultAliasDereferencingProblem = 36
LDAPResultInappropriateAuthentication = 48
LDAPResultInvalidCredentials = 49
LDAPResultInsufficientAccessRights = 50
LDAPResultBusy = 51
LDAPResultUnavailable = 52
LDAPResultUnwillingToPerform = 53
LDAPResultLoopDetect = 54
LDAPResultNamingViolation = 64
LDAPResultObjectClassViolation = 65
LDAPResultNotAllowedOnNonLeaf = 66
LDAPResultNotAllowedOnRDN = 67
LDAPResultEntryAlreadyExists = 68
LDAPResultObjectClassModsProhibited = 69
LDAPResultAffectsMultipleDSAs = 71
LDAPResultOther = 80
ErrorNetwork = 200
ErrorFilterCompile = 201
ErrorFilterDecompile = 202
ErrorDebugging = 203
)
var LDAPResultCodeMap = map[uint8]string{
LDAPResultSuccess: "Success",
LDAPResultOperationsError: "Operations Error",
LDAPResultProtocolError: "Protocol Error",
LDAPResultTimeLimitExceeded: "Time Limit Exceeded",
LDAPResultSizeLimitExceeded: "Size Limit Exceeded",
LDAPResultCompareFalse: "Compare False",
LDAPResultCompareTrue: "Compare True",
LDAPResultAuthMethodNotSupported: "Auth Method Not Supported",
LDAPResultStrongAuthRequired: "Strong Auth Required",
LDAPResultReferral: "Referral",
LDAPResultAdminLimitExceeded: "Admin Limit Exceeded",
LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension",
LDAPResultConfidentialityRequired: "Confidentiality Required",
LDAPResultSaslBindInProgress: "Sasl Bind In Progress",
LDAPResultNoSuchAttribute: "No Such Attribute",
LDAPResultUndefinedAttributeType: "Undefined Attribute Type",
LDAPResultInappropriateMatching: "Inappropriate Matching",
LDAPResultConstraintViolation: "Constraint Violation",
LDAPResultAttributeOrValueExists: "Attribute Or Value Exists",
LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax",
LDAPResultNoSuchObject: "No Such Object",
LDAPResultAliasProblem: "Alias Problem",
LDAPResultInvalidDNSyntax: "Invalid DN Syntax",
LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem",
LDAPResultInappropriateAuthentication: "Inappropriate Authentication",
LDAPResultInvalidCredentials: "Invalid Credentials",
LDAPResultInsufficientAccessRights: "Insufficient Access Rights",
LDAPResultBusy: "Busy",
LDAPResultUnavailable: "Unavailable",
LDAPResultUnwillingToPerform: "Unwilling To Perform",
LDAPResultLoopDetect: "Loop Detect",
LDAPResultNamingViolation: "Naming Violation",
LDAPResultObjectClassViolation: "Object Class Violation",
LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf",
LDAPResultNotAllowedOnRDN: "Not Allowed On RDN",
LDAPResultEntryAlreadyExists: "Entry Already Exists",
LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited",
LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs",
LDAPResultOther: "Other",
}
// Adds descriptions to an LDAP Response packet for debugging
func addLDAPDescriptions(packet *ber.Packet) (err error) {
defer func() {
if r := recover(); r != nil {
err = NewError(ErrorDebugging, errors.New("ldap: cannot process packet to add descriptions"))
}
}()
packet.Description = "LDAP Response"
packet.Children[0].Description = "Message ID"
application := packet.Children[1].Tag
packet.Children[1].Description = ApplicationMap[application]
switch application {
case ApplicationBindRequest:
addRequestDescriptions(packet)
case ApplicationBindResponse:
addDefaultLDAPResponseDescriptions(packet)
case ApplicationUnbindRequest:
addRequestDescriptions(packet)
case ApplicationSearchRequest:
addRequestDescriptions(packet)
case ApplicationSearchResultEntry:
packet.Children[1].Children[0].Description = "Object Name"
packet.Children[1].Children[1].Description = "Attributes"
for _, child := range packet.Children[1].Children[1].Children {
child.Description = "Attribute"
child.Children[0].Description = "Attribute Name"
child.Children[1].Description = "Attribute Values"
for _, grandchild := range child.Children[1].Children {
grandchild.Description = "Attribute Value"
}
}
if len(packet.Children) == 3 {
addControlDescriptions(packet.Children[2])
}
case ApplicationSearchResultDone:
addDefaultLDAPResponseDescriptions(packet)
case ApplicationModifyRequest:
addRequestDescriptions(packet)
case ApplicationModifyResponse:
case ApplicationAddRequest:
addRequestDescriptions(packet)
case ApplicationAddResponse:
case ApplicationDelRequest:
addRequestDescriptions(packet)
case ApplicationDelResponse:
case ApplicationModifyDNRequest:
addRequestDescriptions(packet)
case ApplicationModifyDNResponse:
case ApplicationCompareRequest:
addRequestDescriptions(packet)
case ApplicationCompareResponse:
case ApplicationAbandonRequest:
addRequestDescriptions(packet)
case ApplicationSearchResultReference:
case ApplicationExtendedRequest:
addRequestDescriptions(packet)
case ApplicationExtendedResponse:
}
return nil
}
func addControlDescriptions(packet *ber.Packet) {
packet.Description = "Controls"
for _, child := range packet.Children {
child.Description = "Control"
child.Children[0].Description = "Control Type (" + ControlTypeMap[child.Children[0].Value.(string)] + ")"
value := child.Children[1]
if len(child.Children) == 3 {
child.Children[1].Description = "Criticality"
value = child.Children[2]
}
value.Description = "Control Value"
switch child.Children[0].Value.(string) {
case ControlTypePaging:
value.Description += " (Paging)"
if value.Value != nil {
valueChildren := ber.DecodePacket(value.Data.Bytes())
value.Data.Truncate(0)
value.Value = nil
valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes()
value.AppendChild(valueChildren)
}
value.Children[0].Description = "Real Search Control Value"
value.Children[0].Children[0].Description = "Paging Size"
value.Children[0].Children[1].Description = "Cookie"
}
}
}
func addRequestDescriptions(packet *ber.Packet) {
packet.Description = "LDAP Request"
packet.Children[0].Description = "Message ID"
packet.Children[1].Description = ApplicationMap[packet.Children[1].Tag]
if len(packet.Children) == 3 {
addControlDescriptions(packet.Children[2])
}
}
func addDefaultLDAPResponseDescriptions(packet *ber.Packet) {
resultCode := packet.Children[1].Children[0].Value.(uint64)
packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[uint8(resultCode)] + ")"
packet.Children[1].Children[1].Description = "Matched DN"
packet.Children[1].Children[2].Description = "Error Message"
if len(packet.Children[1].Children) > 3 {
packet.Children[1].Children[3].Description = "Referral"
}
if len(packet.Children) == 3 {
addControlDescriptions(packet.Children[2])
}
}
func DebugBinaryFile(fileName string) error {
file, err := ioutil.ReadFile(fileName)
if err != nil {
return NewError(ErrorDebugging, err)
}
ber.PrintBytes(file, "")
packet := ber.DecodePacket(file)
addLDAPDescriptions(packet)
ber.PrintPacket(packet)
return nil
}
type Error struct {
Err error
ResultCode uint8
}
func (e *Error) Error() string {
return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error())
}
func NewError(resultCode uint8, err error) error {
return &Error{ResultCode: resultCode, Err: err}
}
func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) {
if len(packet.Children) >= 2 {
response := packet.Children[1]
if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) == 3 {
return uint8(response.Children[0].Value.(uint64)), response.Children[2].Value.(string)
}
}
return ErrorNetwork, "Invalid packet format"
}
-123
View File
@@ -1,123 +0,0 @@
package ldap
import (
"fmt"
"testing"
)
var ldapServer = "ldap.itd.umich.edu"
var ldapPort = uint16(389)
var baseDN = "dc=umich,dc=edu"
var filter = []string{
"(cn=cis-fac)",
"(&(objectclass=rfc822mailgroup)(cn=*Computer*))",
"(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"}
var attributes = []string{
"cn",
"description"}
func TestConnect(t *testing.T) {
fmt.Printf("TestConnect: starting...\n")
l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
if err != nil {
t.Errorf(err.Error())
return
}
defer l.Close()
fmt.Printf("TestConnect: finished...\n")
}
func TestSearch(t *testing.T) {
fmt.Printf("TestSearch: starting...\n")
l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
if err != nil {
t.Errorf(err.Error())
return
}
defer l.Close()
searchRequest := NewSearchRequest(
baseDN,
ScopeWholeSubtree, DerefAlways, 0, 0, false,
filter[0],
attributes,
nil)
sr, err := l.Search(searchRequest)
if err != nil {
t.Errorf(err.Error())
return
}
fmt.Printf("TestSearch: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
}
func TestSearchWithPaging(t *testing.T) {
fmt.Printf("TestSearchWithPaging: starting...\n")
l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
if err != nil {
t.Errorf(err.Error())
return
}
defer l.Close()
err = l.Bind("", "")
if err != nil {
t.Errorf(err.Error())
return
}
searchRequest := NewSearchRequest(
baseDN,
ScopeWholeSubtree, DerefAlways, 0, 0, false,
filter[1],
attributes,
nil)
sr, err := l.SearchWithPaging(searchRequest, 5)
if err != nil {
t.Errorf(err.Error())
return
}
fmt.Printf("TestSearchWithPaging: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries))
}
func testMultiGoroutineSearch(t *testing.T, l *Conn, results chan *SearchResult, i int) {
searchRequest := NewSearchRequest(
baseDN,
ScopeWholeSubtree, DerefAlways, 0, 0, false,
filter[i],
attributes,
nil)
sr, err := l.Search(searchRequest)
if err != nil {
t.Errorf(err.Error())
results <- nil
return
}
results <- sr
}
func TestMultiGoroutineSearch(t *testing.T) {
fmt.Printf("TestMultiGoroutineSearch: starting...\n")
l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort))
if err != nil {
t.Errorf(err.Error())
return
}
defer l.Close()
results := make([]chan *SearchResult, len(filter))
for i := range filter {
results[i] = make(chan *SearchResult)
go testMultiGoroutineSearch(t, l, results[i], i)
}
for i := range filter {
sr := <-results[i]
if sr == nil {
t.Errorf("Did not receive results from goroutine for %q", filter[i])
} else {
fmt.Printf("TestMultiGoroutineSearch(%d): %s -> num of entries = %d\n", i, filter[i], len(sr.Entries))
}
}
}
-156
View File
@@ -1,156 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// File contains Modify functionality
//
// https://tools.ietf.org/html/rfc4511
//
// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
// object LDAPDN,
// changes SEQUENCE OF change SEQUENCE {
// operation ENUMERATED {
// add (0),
// delete (1),
// replace (2),
// ... },
// modification PartialAttribute } }
//
// PartialAttribute ::= SEQUENCE {
// type AttributeDescription,
// vals SET OF value AttributeValue }
//
// AttributeDescription ::= LDAPString
// -- Constrained to <attributedescription>
// -- [RFC4512]
//
// AttributeValue ::= OCTET STRING
//
package ldap
import (
"errors"
"log"
"github.com/gogits/gogs/modules/asn1-ber"
)
const (
AddAttribute = 0
DeleteAttribute = 1
ReplaceAttribute = 2
)
type PartialAttribute struct {
attrType string
attrVals []string
}
func (p *PartialAttribute) encode() *ber.Packet {
seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute")
seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.attrType, "Type"))
set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue")
for _, value := range p.attrVals {
set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals"))
}
seq.AppendChild(set)
return seq
}
type ModifyRequest struct {
dn string
addAttributes []PartialAttribute
deleteAttributes []PartialAttribute
replaceAttributes []PartialAttribute
}
func (m *ModifyRequest) Add(attrType string, attrVals []string) {
m.addAttributes = append(m.addAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals})
}
func (m *ModifyRequest) Delete(attrType string, attrVals []string) {
m.deleteAttributes = append(m.deleteAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals})
}
func (m *ModifyRequest) Replace(attrType string, attrVals []string) {
m.replaceAttributes = append(m.replaceAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals})
}
func (m ModifyRequest) encode() *ber.Packet {
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request")
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.dn, "DN"))
changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes")
for _, attribute := range m.addAttributes {
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(AddAttribute), "Operation"))
change.AppendChild(attribute.encode())
changes.AppendChild(change)
}
for _, attribute := range m.deleteAttributes {
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(DeleteAttribute), "Operation"))
change.AppendChild(attribute.encode())
changes.AppendChild(change)
}
for _, attribute := range m.replaceAttributes {
change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change")
change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(ReplaceAttribute), "Operation"))
change.AppendChild(attribute.encode())
changes.AppendChild(change)
}
request.AppendChild(changes)
return request
}
func NewModifyRequest(
dn string,
) *ModifyRequest {
return &ModifyRequest{
dn: dn,
}
}
func (l *Conn) Modify(modifyRequest *ModifyRequest) error {
messageID := l.nextMessageID()
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
packet.AppendChild(modifyRequest.encode())
l.Debug.PrintPacket(packet)
channel, err := l.sendMessage(packet)
if err != nil {
return err
}
if channel == nil {
return NewError(ErrorNetwork, errors.New("ldap: could not send message"))
}
defer l.finishMessage(messageID)
l.Debug.Printf("%d: waiting for response", messageID)
packet = <-channel
l.Debug.Printf("%d: got response %p", messageID, packet)
if packet == nil {
return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
}
if l.Debug {
if err := addLDAPDescriptions(packet); err != nil {
return err
}
ber.PrintPacket(packet)
}
if packet.Children[1].Tag == ApplicationModifyResponse {
resultCode, resultDescription := getLDAPResultCode(packet)
if resultCode != 0 {
return NewError(resultCode, errors.New(resultDescription))
}
} else {
log.Printf("Unexpected Response: %d", packet.Children[1].Tag)
}
l.Debug.Printf("%d: returning", messageID)
return nil
}
-350
View File
@@ -1,350 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// File contains Search functionality
//
// https://tools.ietf.org/html/rfc4511
//
// SearchRequest ::= [APPLICATION 3] SEQUENCE {
// baseObject LDAPDN,
// scope ENUMERATED {
// baseObject (0),
// singleLevel (1),
// wholeSubtree (2),
// ... },
// derefAliases ENUMERATED {
// neverDerefAliases (0),
// derefInSearching (1),
// derefFindingBaseObj (2),
// derefAlways (3) },
// sizeLimit INTEGER (0 .. maxInt),
// timeLimit INTEGER (0 .. maxInt),
// typesOnly BOOLEAN,
// filter Filter,
// attributes AttributeSelection }
//
// AttributeSelection ::= SEQUENCE OF selector LDAPString
// -- The LDAPString is constrained to
// -- <attributeSelector> in Section 4.5.1.8
//
// Filter ::= CHOICE {
// and [0] SET SIZE (1..MAX) OF filter Filter,
// or [1] SET SIZE (1..MAX) OF filter Filter,
// not [2] Filter,
// equalityMatch [3] AttributeValueAssertion,
// substrings [4] SubstringFilter,
// greaterOrEqual [5] AttributeValueAssertion,
// lessOrEqual [6] AttributeValueAssertion,
// present [7] AttributeDescription,
// approxMatch [8] AttributeValueAssertion,
// extensibleMatch [9] MatchingRuleAssertion,
// ... }
//
// SubstringFilter ::= SEQUENCE {
// type AttributeDescription,
// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE {
// initial [0] AssertionValue, -- can occur at most once
// any [1] AssertionValue,
// final [2] AssertionValue } -- can occur at most once
// }
//
// MatchingRuleAssertion ::= SEQUENCE {
// matchingRule [1] MatchingRuleId OPTIONAL,
// type [2] AttributeDescription OPTIONAL,
// matchValue [3] AssertionValue,
// dnAttributes [4] BOOLEAN DEFAULT FALSE }
//
//
package ldap
import (
"errors"
"fmt"
"strings"
"github.com/gogits/gogs/modules/asn1-ber"
)
const (
ScopeBaseObject = 0
ScopeSingleLevel = 1
ScopeWholeSubtree = 2
)
var ScopeMap = map[int]string{
ScopeBaseObject: "Base Object",
ScopeSingleLevel: "Single Level",
ScopeWholeSubtree: "Whole Subtree",
}
const (
NeverDerefAliases = 0
DerefInSearching = 1
DerefFindingBaseObj = 2
DerefAlways = 3
)
var DerefMap = map[int]string{
NeverDerefAliases: "NeverDerefAliases",
DerefInSearching: "DerefInSearching",
DerefFindingBaseObj: "DerefFindingBaseObj",
DerefAlways: "DerefAlways",
}
type Entry struct {
DN string
Attributes []*EntryAttribute
}
func (e *Entry) GetAttributeValues(attribute string) []string {
for _, attr := range e.Attributes {
if attr.Name == attribute {
return attr.Values
}
}
return []string{}
}
func (e *Entry) GetAttributeValue(attribute string) string {
values := e.GetAttributeValues(attribute)
if len(values) == 0 {
return ""
}
return values[0]
}
func (e *Entry) Print() {
fmt.Printf("DN: %s\n", e.DN)
for _, attr := range e.Attributes {
attr.Print()
}
}
func (e *Entry) PrettyPrint(indent int) {
fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN)
for _, attr := range e.Attributes {
attr.PrettyPrint(indent + 2)
}
}
type EntryAttribute struct {
Name string
Values []string
}
func (e *EntryAttribute) Print() {
fmt.Printf("%s: %s\n", e.Name, e.Values)
}
func (e *EntryAttribute) PrettyPrint(indent int) {
fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values)
}
type SearchResult struct {
Entries []*Entry
Referrals []string
Controls []Control
}
func (s *SearchResult) Print() {
for _, entry := range s.Entries {
entry.Print()
}
}
func (s *SearchResult) PrettyPrint(indent int) {
for _, entry := range s.Entries {
entry.PrettyPrint(indent)
}
}
type SearchRequest struct {
BaseDN string
Scope int
DerefAliases int
SizeLimit int
TimeLimit int
TypesOnly bool
Filter string
Attributes []string
Controls []Control
}
func (s *SearchRequest) encode() (*ber.Packet, error) {
request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request")
request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, s.BaseDN, "Base DN"))
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.Scope), "Scope"))
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.DerefAliases), "Deref Aliases"))
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.SizeLimit), "Size Limit"))
request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.TimeLimit), "Time Limit"))
request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, s.TypesOnly, "Types Only"))
// compile and encode filter
filterPacket, err := CompileFilter(s.Filter)
if err != nil {
return nil, err
}
request.AppendChild(filterPacket)
// encode attributes
attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
for _, attribute := range s.Attributes {
attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute"))
}
request.AppendChild(attributesPacket)
return request, nil
}
func NewSearchRequest(
BaseDN string,
Scope, DerefAliases, SizeLimit, TimeLimit int,
TypesOnly bool,
Filter string,
Attributes []string,
Controls []Control,
) *SearchRequest {
return &SearchRequest{
BaseDN: BaseDN,
Scope: Scope,
DerefAliases: DerefAliases,
SizeLimit: SizeLimit,
TimeLimit: TimeLimit,
TypesOnly: TypesOnly,
Filter: Filter,
Attributes: Attributes,
Controls: Controls,
}
}
func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) {
if searchRequest.Controls == nil {
searchRequest.Controls = make([]Control, 0)
}
pagingControl := NewControlPaging(pagingSize)
searchRequest.Controls = append(searchRequest.Controls, pagingControl)
searchResult := new(SearchResult)
for {
result, err := l.Search(searchRequest)
l.Debug.Printf("Looking for Paging Control...")
if err != nil {
return searchResult, err
}
if result == nil {
return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received"))
}
for _, entry := range result.Entries {
searchResult.Entries = append(searchResult.Entries, entry)
}
for _, referral := range result.Referrals {
searchResult.Referrals = append(searchResult.Referrals, referral)
}
for _, control := range result.Controls {
searchResult.Controls = append(searchResult.Controls, control)
}
l.Debug.Printf("Looking for Paging Control...")
pagingResult := FindControl(result.Controls, ControlTypePaging)
if pagingResult == nil {
pagingControl = nil
l.Debug.Printf("Could not find paging control. Breaking...")
break
}
cookie := pagingResult.(*ControlPaging).Cookie
if len(cookie) == 0 {
pagingControl = nil
l.Debug.Printf("Could not find cookie. Breaking...")
break
}
pagingControl.SetCookie(cookie)
}
if pagingControl != nil {
l.Debug.Printf("Abandoning Paging...")
pagingControl.PagingSize = 0
l.Search(searchRequest)
}
return searchResult, nil
}
func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) {
messageID := l.nextMessageID()
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID"))
// encode search request
encodedSearchRequest, err := searchRequest.encode()
if err != nil {
return nil, err
}
packet.AppendChild(encodedSearchRequest)
// encode search controls
if searchRequest.Controls != nil {
packet.AppendChild(encodeControls(searchRequest.Controls))
}
l.Debug.PrintPacket(packet)
channel, err := l.sendMessage(packet)
if err != nil {
return nil, err
}
if channel == nil {
return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message"))
}
defer l.finishMessage(messageID)
result := &SearchResult{
Entries: make([]*Entry, 0),
Referrals: make([]string, 0),
Controls: make([]Control, 0)}
foundSearchResultDone := false
for !foundSearchResultDone {
l.Debug.Printf("%d: waiting for response", messageID)
packet = <-channel
l.Debug.Printf("%d: got response %p", messageID, packet)
if packet == nil {
return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message"))
}
if l.Debug {
if err := addLDAPDescriptions(packet); err != nil {
return nil, err
}
ber.PrintPacket(packet)
}
switch packet.Children[1].Tag {
case 4:
entry := new(Entry)
entry.DN = packet.Children[1].Children[0].Value.(string)
for _, child := range packet.Children[1].Children[1].Children {
attr := new(EntryAttribute)
attr.Name = child.Children[0].Value.(string)
for _, value := range child.Children[1].Children {
attr.Values = append(attr.Values, value.Value.(string))
}
entry.Attributes = append(entry.Attributes, attr)
}
result.Entries = append(result.Entries, entry)
case 5:
resultCode, resultDescription := getLDAPResultCode(packet)
if resultCode != 0 {
return result, NewError(resultCode, errors.New(resultDescription))
}
if len(packet.Children) == 3 {
for _, child := range packet.Children[2].Children {
result.Controls = append(result.Controls, DecodeControl(child))
}
}
foundSearchResultDone = true
case 19:
result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string))
}
}
l.Debug.Printf("%d: returning", messageID)
return result, nil
}
+1 -1
View File
@@ -54,7 +54,7 @@ func RenderToPng(params *RenderOpts) (string, error) {
}()
select {
case <-time.After(10 * time.Second):
case <-time.After(15 * time.Second):
if err := cmd.Process.Kill(); err != nil {
log.Error(4, "failed to kill: %v", err)
}
+9 -1
View File
@@ -5,7 +5,7 @@ import (
"time"
)
// Events can be passed to external systems via for example AMPQ
// Events can be passed to external systems via for example AMQP
// Treat these events as basically DTOs so changes has to be backward compatible
type Priority string
@@ -70,6 +70,14 @@ type UserCreated struct {
Email string `json:"email"`
}
type UserSignedUp struct {
Timestamp time.Time `json:"timestamp"`
Id int64 `json:"id"`
Name string `json:"name"`
Login string `json:"login"`
Email string `json:"email"`
}
type UserUpdated struct {
Timestamp time.Time `json:"timestamp"`
Id int64 `json:"id"`
+2
View File
@@ -60,8 +60,10 @@ func getCreateUserCommandForProxyAuth(headerVal string) *m.CreateUserCommand {
cmd := m.CreateUserCommand{}
if setting.AuthProxyHeaderProperty == "username" {
cmd.Login = headerVal
cmd.Email = headerVal
} else if setting.AuthProxyHeaderProperty == "email" {
cmd.Email = headerVal
cmd.Login = headerVal
} else {
panic("Auth proxy header property invalid")
}
+46 -3
View File
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
type Context struct {
@@ -40,6 +41,7 @@ func GetContextHandler() macaron.Handler {
// then look for api key in session (special case for render calls via api)
// then test if anonymous access is enabled
if initContextWithApiKey(ctx) ||
initContextWithBasicAuth(ctx) ||
initContextWithAuthProxy(ctx) ||
initContextWithUserSessionCookie(ctx) ||
initContextWithApiKeyFromSession(ctx) ||
@@ -128,6 +130,47 @@ func initContextWithApiKey(ctx *Context) bool {
}
}
func initContextWithBasicAuth(ctx *Context) bool {
if !setting.BasicAuthEnabled {
return false
}
header := ctx.Req.Header.Get("Authorization")
if header == "" {
return false
}
username, password, err := util.DecodeBasicAuthHeader(header)
if err != nil {
ctx.JsonApiErr(401, "Invalid Basic Auth Header", err)
return true
}
loginQuery := m.GetUserByLoginQuery{LoginOrEmail: username}
if err := bus.Dispatch(&loginQuery); err != nil {
ctx.JsonApiErr(401, "Basic auth failed", err)
return true
}
user := loginQuery.Result
// validate password
if util.EncodePassword(password, user.Salt) != user.Password {
ctx.JsonApiErr(401, "Invalid username or password", nil)
return true
}
query := m.GetSignedInUserQuery{UserId: user.Id}
if err := bus.Dispatch(&query); err != nil {
ctx.JsonApiErr(401, "Authentication error", err)
return true
} else {
ctx.SignedInUser = query.Result
ctx.IsSignedIn = true
return true
}
}
// special case for panel render calls with api key
func initContextWithApiKeyFromSession(ctx *Context) bool {
keyId := ctx.Session.Get(SESS_KEY_APIKEY)
@@ -197,10 +240,10 @@ func (ctx *Context) JsonApiErr(status int, message string, err error) {
switch status {
case 404:
resp["message"] = "Not Found"
metrics.M_Api_Status_500.Inc(1)
case 500:
metrics.M_Api_Status_404.Inc(1)
resp["message"] = "Not Found"
case 500:
metrics.M_Api_Status_500.Inc(1)
resp["message"] = "Internal Server Error"
}
+36
View File
@@ -48,6 +48,32 @@ func TestMiddlewareContext(t *testing.T) {
})
})
middlewareScenario("Using basic auth", func(sc *scenarioContext) {
bus.AddHandler("test", func(query *m.GetUserByLoginQuery) error {
query.Result = &m.User{
Password: util.EncodePassword("myPass", "salt"),
Salt: "salt",
}
return nil
})
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 2, UserId: 12}
return nil
})
setting.BasicAuthEnabled = true
authHeader := util.GetBasicAuthHeader("myUser", "myPass")
sc.fakeReq("GET", "/").withAuthoriziationHeader(authHeader).exec()
Convey("Should init middleware context with user", func() {
So(sc.context.IsSignedIn, ShouldEqual, true)
So(sc.context.OrgId, ShouldEqual, 2)
So(sc.context.UserId, ShouldEqual, 12)
})
})
middlewareScenario("Valid api key", func(sc *scenarioContext) {
keyhash := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd")
@@ -223,6 +249,7 @@ type scenarioContext struct {
context *Context
resp *httptest.ResponseRecorder
apiKey string
authHeader string
respJson map[string]interface{}
handlerFunc handlerFunc
defaultHandler macaron.Handler
@@ -240,6 +267,11 @@ func (sc *scenarioContext) withInvalidApiKey() *scenarioContext {
return sc
}
func (sc *scenarioContext) withAuthoriziationHeader(authHeader string) *scenarioContext {
sc.authHeader = authHeader
return sc
}
func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
sc.resp = httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil)
@@ -266,6 +298,10 @@ func (sc *scenarioContext) exec() {
sc.req.Header.Add("Authorization", "Bearer "+sc.apiKey)
}
if sc.authHeader != "" {
sc.req.Header.Add("Authorization", sc.authHeader)
}
sc.m.ServeHTTP(sc.resp, sc.req)
if sc.resp.Header().Get("Content-Type") == "application/json; charset=UTF-8" {
+3 -2
View File
@@ -5,12 +5,13 @@ import (
"strings"
"time"
"github.com/dalu/slug"
"github.com/gosimple/slug"
)
// Typed errors
var (
ErrDashboardNotFound = errors.New("Dashboard not found")
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
ErrDashboardWithSameNameExists = errors.New("A dashboard with the same name already exists")
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
)
@@ -49,7 +50,7 @@ func NewDashboard(title string) *Dashboard {
// GetTags turns the tags in data json into go string array
func (dash *Dashboard) GetTags() []string {
jsonTags := dash.Data["tags"]
if jsonTags == nil {
if jsonTags == nil || jsonTags == "" {
return []string{}
}
@@ -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
View File
@@ -0,0 +1,22 @@
package models
import "errors"
var ErrInvalidEmailCode = errors.New("Invalid or expired email code")
type SendEmailCommand struct {
To []string
Template string
Data map[string]interface{}
Massive bool
Info string
}
type SendResetPasswordEmailCommand struct {
User *User
}
type ValidateResetPasswordCodeQuery struct {
Code string
Result *User
}
-4
View File
@@ -1,7 +1,5 @@
package models
import "errors"
type OAuthType int
const (
@@ -9,5 +7,3 @@ const (
GOOGLE
TWITTER
)
var ErrNotFound = errors.New("Not found")
+10
View File
@@ -30,6 +30,16 @@ type User struct {
Updated time.Time
}
func (u *User) NameOrFallback() string {
if u.Name != "" {
return u.Name
} else if u.Login != "" {
return u.Login
} else {
return u.Email
}
}
// ---------------------
// COMMANDS
+98
View File
@@ -0,0 +1,98 @@
package notifications
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"time"
"github.com/Unknwon/com"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
const timeLimitCodeLength = 12 + 6 + 40
// create a time limit code
// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string
func createTimeLimitCode(data string, minutes int, startInf interface{}) string {
format := "200601021504"
var start, end time.Time
var startStr, endStr string
if startInf == nil {
// Use now time create code
start = time.Now()
startStr = start.Format(format)
} else {
// use start string create code
startStr = startInf.(string)
start, _ = time.ParseInLocation(format, startStr, time.Local)
startStr = start.Format(format)
}
end = start.Add(time.Minute * time.Duration(minutes))
endStr = end.Format(format)
// create sha1 encode string
sh := sha1.New()
sh.Write([]byte(data + setting.SecretKey + startStr + endStr + com.ToStr(minutes)))
encoded := hex.EncodeToString(sh.Sum(nil))
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
return code
}
// verify time limit code
func validateUserEmailCode(user *m.User, code string) bool {
if len(code) <= 18 {
return false
}
minutes := setting.EmailCodeValidMinutes
code = code[:timeLimitCodeLength]
// split code
start := code[:12]
lives := code[12:18]
if d, err := com.StrTo(lives).Int(); err == nil {
minutes = d
}
// right active code
data := com.ToStr(user.Id) + user.Email + user.Login + user.Password + user.Rands
retCode := createTimeLimitCode(data, minutes, start)
fmt.Printf("code : %s\ncode2: %s", retCode, code)
if retCode == code && minutes > 0 {
// check time is expired or not
before, _ := time.ParseInLocation("200601021504", start, time.Local)
now := time.Now()
if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() {
return true
}
}
return false
}
func getLoginForEmailCode(code string) string {
if len(code) <= timeLimitCodeLength {
return ""
}
// use tail hex username query user
hexStr := code[timeLimitCodeLength:]
b, _ := hex.DecodeString(hexStr)
return string(b)
}
func createUserEmailCode(u *m.User, startInf interface{}) string {
minutes := setting.EmailCodeValidMinutes
data := com.ToStr(u.Id) + u.Email + u.Login + u.Password + u.Rands
code := createTimeLimitCode(data, minutes, startInf)
// add tail hex username
code += hex.EncodeToString([]byte(u.Login))
return code
}
+35
View File
@@ -0,0 +1,35 @@
package notifications
import (
"testing"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
func TestEmailCodes(t *testing.T) {
Convey("When generating code", t, func() {
setting.EmailCodeValidMinutes = 120
user := &m.User{Id: 10, Email: "t@a.com", Login: "asd", Password: "1", Rands: "2"}
code := createUserEmailCode(user, nil)
Convey("getLoginForCode should return login", func() {
login := getLoginForEmailCode(code)
So(login, ShouldEqual, "asd")
})
Convey("Can verify valid code", func() {
So(validateUserEmailCode(user, code), ShouldBeTrue)
})
Convey("Cannot verify in-valid code", func() {
code = "ASD"
So(validateUserEmailCode(user, code), ShouldBeFalse)
})
})
}
+33
View File
@@ -0,0 +1,33 @@
package notifications
import (
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
type Message struct {
To []string
From string
Subject string
Body string
Massive bool
Info string
}
// create mail content
func (m *Message) Content() string {
contentType := "text/html; charset=UTF-8"
content := "From: " + m.From + "\r\nSubject: " + m.Subject + "\r\nContent-Type: " + contentType + "\r\n\r\n" + m.Body
return content
}
func setDefaultTemplateData(data map[string]interface{}, u *m.User) {
data["AppUrl"] = setting.AppUrl
data["BuildVersion"] = setting.BuildVersion
data["BuildStamp"] = setting.BuildStamp
data["EmailCodeValidHours"] = setting.EmailCodeValidMinutes / 60
data["Subject"] = map[string]interface{}{}
if u != nil {
data["Name"] = u.NameOrFallback()
}
}
+186
View File
@@ -0,0 +1,186 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package notifications
import (
"crypto/tls"
"fmt"
"net"
"net/mail"
"net/smtp"
"os"
"strings"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
)
var mailQueue chan *Message
func initMailQueue() {
mailQueue = make(chan *Message, 10)
go processMailQueue()
}
func processMailQueue() {
for {
select {
case msg := <-mailQueue:
num, err := buildAndSend(msg)
tos := strings.Join(msg.To, "; ")
info := ""
if err != nil {
if len(msg.Info) > 0 {
info = ", info: " + msg.Info
}
log.Error(4, fmt.Sprintf("Async sent email %d succeed, not send emails: %s%s err: %s", num, tos, info, err))
} else {
log.Trace(fmt.Sprintf("Async sent email %d succeed, sent emails: %s%s", num, tos, info))
}
}
}
}
var addToMailQueue = func(msg *Message) {
mailQueue <- msg
}
func sendToSmtpServer(recipients []string, msgContent []byte) error {
host, port, err := net.SplitHostPort(setting.Smtp.Host)
if err != nil {
return err
}
tlsconfig := &tls.Config{
InsecureSkipVerify: setting.Smtp.SkipVerify,
ServerName: host,
}
if setting.Smtp.CertFile != "" {
cert, err := tls.LoadX509KeyPair(setting.Smtp.CertFile, setting.Smtp.KeyFile)
if err != nil {
return err
}
tlsconfig.Certificates = []tls.Certificate{cert}
}
conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
if err != nil {
return err
}
defer conn.Close()
isSecureConn := false
// Start TLS directly if the port ends with 465 (SMTPS protocol)
if strings.HasSuffix(port, "465") {
conn = tls.Client(conn, tlsconfig)
isSecureConn = true
}
client, err := smtp.NewClient(conn, host)
if err != nil {
return err
}
hostname, err := os.Hostname()
if err != nil {
return err
}
if err = client.Hello(hostname); err != nil {
return err
}
// If not using SMTPS, alway use STARTTLS if available
hasStartTLS, _ := client.Extension("STARTTLS")
if !isSecureConn && hasStartTLS {
if err = client.StartTLS(tlsconfig); err != nil {
return err
}
}
canAuth, options := client.Extension("AUTH")
if canAuth && len(setting.Smtp.User) > 0 {
var auth smtp.Auth
if strings.Contains(options, "CRAM-MD5") {
auth = smtp.CRAMMD5Auth(setting.Smtp.User, setting.Smtp.Password)
} else if strings.Contains(options, "PLAIN") {
auth = smtp.PlainAuth("", setting.Smtp.User, setting.Smtp.Password, host)
}
if auth != nil {
if err = client.Auth(auth); err != nil {
return err
}
}
}
if fromAddress, err := mail.ParseAddress(setting.Smtp.FromAddress); err != nil {
return err
} else {
if err = client.Mail(fromAddress.Address); err != nil {
return err
}
}
for _, rec := range recipients {
if err = client.Rcpt(rec); err != nil {
return err
}
}
w, err := client.Data()
if err != nil {
return err
}
if _, err = w.Write([]byte(msgContent)); err != nil {
return err
}
if err = w.Close(); err != nil {
return err
}
return client.Quit()
}
func buildAndSend(msg *Message) (int, error) {
log.Trace("Sending mails to: %s", strings.Join(msg.To, "; "))
// get message body
content := msg.Content()
if len(msg.To) == 0 {
return 0, fmt.Errorf("empty receive emails")
} else if len(msg.Body) == 0 {
return 0, fmt.Errorf("empty email body")
}
if msg.Massive {
// send mail to multiple emails one by one
num := 0
for _, to := range msg.To {
body := []byte("To: " + to + "\r\n" + content)
err := sendToSmtpServer([]string{to}, body)
if err != nil {
return num, err
}
num++
}
return num, nil
} else {
body := []byte("To: " + strings.Join(msg.To, ";") + "\r\n" + content)
// send to multiple emails in one message
err := sendToSmtpServer(msg.To, body)
if err != nil {
return 0, err
} else {
return 1, nil
}
}
}
+137
View File
@@ -0,0 +1,137 @@
package notifications
import (
"bytes"
"errors"
"html/template"
"path/filepath"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
var mailTemplates *template.Template
var tmplResetPassword = "reset_password.html"
var tmplWelcomeOnSignUp = "welcome_on_signup.html"
func Init() error {
initMailQueue()
bus.AddHandler("email", sendResetPasswordEmail)
bus.AddHandler("email", validateResetPasswordCode)
bus.AddHandler("email", sendEmailCommandHandler)
bus.AddEventListener(userSignedUpHandler)
mailTemplates = template.New("name")
mailTemplates.Funcs(template.FuncMap{
"Subject": subjectTemplateFunc,
})
templatePattern := filepath.Join(setting.StaticRootPath, setting.Smtp.TemplatesPattern)
_, err := mailTemplates.ParseGlob(templatePattern)
if err != nil {
return err
}
if !util.IsEmail(setting.Smtp.FromAddress) {
return errors.New("Invalid email address for smpt from_adress config")
}
if setting.EmailCodeValidMinutes == 0 {
setting.EmailCodeValidMinutes = 120
}
return nil
}
func subjectTemplateFunc(obj map[string]interface{}, value string) string {
obj["value"] = value
return ""
}
func sendEmailCommandHandler(cmd *m.SendEmailCommand) error {
if !setting.Smtp.Enabled {
return errors.New("Grafana mailing/smtp options not configured, contact your Grafana admin")
}
var buffer bytes.Buffer
data := cmd.Data
if data == nil {
data = make(map[string]interface{}, 10)
}
setDefaultTemplateData(data, nil)
mailTemplates.ExecuteTemplate(&buffer, cmd.Template, data)
subjectTmplText := data["Subject"].(map[string]interface{})["value"].(string)
subjectTmpl, err := template.New("subject").Parse(subjectTmplText)
if err != nil {
return err
}
var subjectBuffer bytes.Buffer
err = subjectTmpl.ExecuteTemplate(&subjectBuffer, "subject", data)
if err != nil {
return err
}
addToMailQueue(&Message{
To: cmd.To,
From: setting.Smtp.FromAddress,
Subject: subjectBuffer.String(),
Body: buffer.String(),
})
return nil
}
func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
return sendEmailCommandHandler(&m.SendEmailCommand{
To: []string{cmd.User.Email},
Template: tmplResetPassword,
Data: map[string]interface{}{
"Code": createUserEmailCode(cmd.User, nil),
"Name": cmd.User.NameOrFallback(),
},
})
}
func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error {
login := getLoginForEmailCode(query.Code)
if login == "" {
return m.ErrInvalidEmailCode
}
userQuery := m.GetUserByLoginQuery{LoginOrEmail: login}
if err := bus.Dispatch(&userQuery); err != nil {
return err
}
if !validateUserEmailCode(userQuery.Result, query.Code) {
return m.ErrInvalidEmailCode
}
query.Result = userQuery.Result
return nil
}
func userSignedUpHandler(evt *events.UserSignedUp) error {
log.Info("User signed up: %s, send_option: %s", evt.Email, setting.Smtp.SendWelcomeEmailOnSignUp)
if evt.Email == "" || !setting.Smtp.SendWelcomeEmailOnSignUp {
return nil
}
return sendEmailCommandHandler(&m.SendEmailCommand{
To: []string{evt.Email},
Template: tmplWelcomeOnSignUp,
Data: map[string]interface{}{
"Name": evt.Login,
},
})
}
@@ -0,0 +1,39 @@
package notifications
import (
"testing"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
func TestNotifications(t *testing.T) {
Convey("Given the notifications service", t, func() {
bus.ClearBusHandlers()
setting.StaticRootPath = "../../../public/"
setting.Smtp.Enabled = true
setting.Smtp.TemplatesPattern = "emails/*.html"
setting.Smtp.FromAddress = "from@address.com"
err := Init()
So(err, ShouldBeNil)
var sentMsg *Message
addToMailQueue = func(msg *Message) {
sentMsg = msg
}
Convey("When sending reset email password", func() {
err := sendResetPasswordEmail(&m.SendResetPasswordEmailCommand{User: &m.User{Email: "asd@asd.com"}})
So(err, ShouldBeNil)
So(sentMsg.Body, ShouldContainSubstring, "body")
So(sentMsg.Subject, ShouldEqual, "Reset your Grafana password - asd@asd.com")
So(sentMsg.Body, ShouldNotContainSubstring, "Subject")
})
})
}
@@ -1,6 +1,7 @@
package search
import (
"log"
"path/filepath"
"sort"
@@ -15,6 +16,12 @@ func Init() {
bus.AddHandler("search", searchHandler)
jsonIndexCfg, _ := setting.Cfg.GetSection("dashboards.json")
if jsonIndexCfg == nil {
log.Fatal("Config section missing: dashboards.json")
return
}
jsonIndexEnabled := jsonIndexCfg.Key("enabled").MustBool(false)
if jsonIndexEnabled {
@@ -11,7 +11,7 @@ import (
func TestSearch(t *testing.T) {
Convey("Given search query", t, func() {
jsonDashIndex = NewJsonDashIndex("../../public/dashboards/")
jsonDashIndex = NewJsonDashIndex("../../../public/dashboards/")
query := Query{Limit: 2000}
bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error {
@@ -51,13 +51,15 @@ func (index *JsonDashIndex) Search(query *Query) ([]*Hit, error) {
return results, nil
}
queryStr := strings.ToLower(query.Title)
for _, item := range index.items {
if len(results) > query.Limit {
break
}
// add results with matchig title filter
if strings.Contains(item.TitleLower, query.Title) {
if strings.Contains(item.TitleLower, queryStr) {
results = append(results, &Hit{
Type: DashHitJson,
Title: item.Dashboard.Title,
@@ -9,7 +9,7 @@ import (
func TestJsonDashIndex(t *testing.T) {
Convey("Given the json dash index", t, func() {
index := NewJsonDashIndex("../../public/dashboards/")
index := NewJsonDashIndex("../../../public/dashboards/")
Convey("Should be able to update index", func() {
err := index.updateIndex()
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/search"
"github.com/grafana/grafana/pkg/services/search"
)
func init() {
+1 -1
View File
@@ -57,7 +57,7 @@ func GetDashboardSnapshot(query *m.GetDashboardSnapshotQuery) error {
if err != nil {
return err
} else if has == false {
return m.ErrNotFound
return m.ErrDashboardSnapshotNotFound
}
query.Result = &snapshot
+1 -1
View File
@@ -6,7 +6,7 @@ import (
. "github.com/smartystreets/goconvey/convey"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/search"
"github.com/grafana/grafana/pkg/services/search"
)
func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
+18 -11
View File
@@ -38,7 +38,6 @@ const (
var (
// App settings.
Env string = DEV
AppName string
AppUrl string
AppSubUrl string
@@ -68,18 +67,18 @@ var (
EnforceDomain bool
// Security settings.
SecretKey string
LogInRememberDays int
CookieUserName string
CookieRememberName string
DisableGravatar bool
SecretKey string
LogInRememberDays int
CookieUserName string
CookieRememberName string
DisableGravatar bool
EmailCodeValidMinutes int
// User settings
AllowUserSignUp bool
AllowUserOrgCreate bool
AutoAssignOrg bool
AutoAssignOrgRole string
ViewerRoleMode string
// Http auth
AdminUser string
@@ -95,6 +94,9 @@ var (
AuthProxyHeaderProperty string
AuthProxyAutoSignUp bool
// Basic Auth
BasicAuthEnabled bool
// Session settings.
SessionOptions session.Options
@@ -117,7 +119,10 @@ var (
// LDAP
LdapEnabled bool
LdapUrls []string
LdapHosts []string
// SMTP email settings
Smtp SmtpSettings
)
type CommandLineArgs struct {
@@ -351,7 +356,6 @@ func NewConfigContext(args *CommandLineArgs) {
setHomePath(args)
loadConfiguration(args)
AppName = Cfg.Section("").Key("app_name").MustString("Grafana")
Env = Cfg.Section("").Key("app_mode").MustString("development")
server := Cfg.Section("server")
@@ -388,7 +392,6 @@ func NewConfigContext(args *CommandLineArgs) {
AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
ViewerRoleMode = users.Key("viewer_role_mode").In("default", []string{"default", "strinct"})
// anonymous access
AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
@@ -402,6 +405,9 @@ func NewConfigContext(args *CommandLineArgs) {
AuthProxyHeaderProperty = authProxy.Key("header_property").String()
AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
authBasic := Cfg.Section("auth.basic")
BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
// PhantomJS rendering
ImagesDir = filepath.Join(DataPath, "png")
PhantomDir = filepath.Join(HomePath, "vendor/phantomjs")
@@ -412,9 +418,10 @@ func NewConfigContext(args *CommandLineArgs) {
ldapSec := Cfg.Section("auth.ldap")
LdapEnabled = ldapSec.Key("enabled").MustBool(false)
LdapUrls = ldapSec.Key("urls").Strings(" ")
LdapHosts = ldapSec.Key("hosts").Strings(" ")
readSessionConfig()
readSmtpSettings()
}
func readSessionConfig() {
+31
View File
@@ -0,0 +1,31 @@
package setting
type SmtpSettings struct {
Enabled bool
Host string
User string
Password string
CertFile string
KeyFile string
FromAddress string
SkipVerify bool
SendWelcomeEmailOnSignUp bool
TemplatesPattern string
}
func readSmtpSettings() {
sec := Cfg.Section("smtp")
Smtp.Enabled = sec.Key("enabled").MustBool(false)
Smtp.Host = sec.Key("host").String()
Smtp.User = sec.Key("user").String()
Smtp.Password = sec.Key("password").String()
Smtp.CertFile = sec.Key("cert_file").String()
Smtp.KeyFile = sec.Key("key_file").String()
Smtp.FromAddress = sec.Key("from_address").String()
Smtp.SkipVerify = sec.Key("skip_verify").MustBool(false)
emails := Cfg.Section("emails")
Smtp.SendWelcomeEmailOnSignUp = emails.Key("welcome_email_on_sign_up").MustBool(false)
Smtp.TemplatesPattern = emails.Key("templates_pattern").MustString("emails/*.html")
}
-1
View File
@@ -15,7 +15,6 @@ func TestLoadingSettings(t *testing.T) {
Convey("Given the default ini files", func() {
NewConfigContext(&CommandLineArgs{HomePath: "../../"})
So(AppName, ShouldEqual, "Grafana")
So(AdminUser, ShouldEqual, "admin")
})
+22
View File
@@ -7,8 +7,10 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"hash"
"strings"
)
// source: https://github.com/gogits/gogs/blob/9ee80e3e5426821f03a4e99fad34418f5c736413/modules/base/tool.go#L58
@@ -80,3 +82,23 @@ func GetBasicAuthHeader(user string, password string) string {
var userAndPass = user + ":" + password
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userAndPass))
}
func DecodeBasicAuthHeader(header string) (string, string, error) {
var code string
parts := strings.SplitN(header, " ", 2)
if len(parts) == 2 && parts[0] == "Basic" {
code = parts[1]
}
decoded, err := base64.StdEncoding.DecodeString(code)
if err != nil {
return "", "", err
}
userAndPass := strings.SplitN(string(decoded), ":", 2)
if len(userAndPass) != 2 {
return "", "", errors.New("Invalid basic auth header")
}
return userAndPass[0], userAndPass[1], nil
}
+10
View File
@@ -13,4 +13,14 @@ func TestEncoding(t *testing.T) {
So(result, ShouldEqual, "Basic Z3JhZmFuYToxMjM0")
})
Convey("When decoding basic auth header", t, func() {
header := GetBasicAuthHeader("grafana", "1234")
username, password, err := DecodeBasicAuthHeader(header)
So(err, ShouldBeNil)
So(username, ShouldEqual, "grafana")
So(password, ShouldEqual, "1234")
})
}
+18
View File
@@ -0,0 +1,18 @@
package util
import (
"regexp"
"strings"
)
const (
emailRegexPattern string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
)
var (
regexEmail = regexp.MustCompile(emailRegexPattern)
)
func IsEmail(str string) bool {
return regexEmail.MatchString(strings.ToLower(str))
}