Started work on LDAP again, #1450
This commit is contained in:
+9
-1
@@ -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
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
Reference in New Issue
Block a user