<commit_before>/*
   Copyright 2015 Cesanta Software Ltd.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       https://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package authn

import (
	"bytes"
	"crypto/tls"
	b64 "encoding/base64"
	"fmt"
	"strings"

	"github.com/go-ldap/ldap"
	"github.com/golang/glog"
)

type LDAPAuthConfig struct {
	Addr        string `yaml:"addr,omitempty"`
	StartTLS    bool   `yaml:"start_tls,omitempty"`
	Base        string `yaml:"base,omitempty"`
	Filter      string `yaml:"filter,omitempty"`
	Bind_DN     string `yaml:"bind_dn,omitempty"`
	Password    string `yaml:"password,omitempty"`
	GroupBaseDN string `yaml:"group_base_dn,omitempty"`
	GroupFilter string `yaml:"group_filter,omitempty"`
}

type LDAPAuth struct {
	config *LDAPAuthConfig
}

func NewLDAPAuth(c *LDAPAuthConfig) (*LDAPAuth, error) {
	return &LDAPAuth{
		config: c,
	}, nil
}

//How to authenticate user, please refer to https://github.com/go-ldap/ldap/blob/master/example_test.go#L166
func (la *LDAPAuth) Authenticate(account string, password PasswordString) (bool, error) {
	if account == "" {
		return false, NoMatch
	}
	l, err := la.ldapConnection()
	if err != nil {
		return false, err
	}
	defer l.Close()

	// First bind with a read only user, to prevent the following search won't perform any write action
	if bindErr := la.bindReadOnlyUser(l); bindErr != nil {
		return false, bindErr
	}

	if !la.isAccountSafe(account) {
		return false, fmt.Errorf("The account which is trying to login is destructive. The request is reject.")
	}

	filter := la.getFilter(account)
	accountEntryDN, uSearchErr := la.ldapSearch(l, &la.config.Base, &filter, &[]string{})
	if uSearchErr != nil {
		return false, uSearchErr
	}
	// Bind as the user to verify their password
	if len(accountEntryDN) > 0 {
		err := l.Bind(accountEntryDN, string(password))
		if err != nil {
			return false, err
		}
	}
	// Rebind as the read only user for any futher queries
	if bindErr := la.bindReadOnlyUser(l); bindErr != nil {
		return false, bindErr
	}

	return true, nil
}

func (la *LDAPAuth) bindReadOnlyUser(l *ldap.Conn) error {
	if la.config.Bind_DN != "" {
		glog.V(2).Infof("Bind read-only user")
		sDec, _ := b64.StdEncoding.DecodeString(la.config.Password)
		err := l.Bind(la.config.Bind_DN, string(sDec))
		if err != nil {
			return err
		}
	}
	return nil
}

//To prevent LDAP injection, some characters must be escaped or restricted for searching
//character \ ( ) ! should not be allowed in filter, because those characters will result in filter compiler error. Escaping them by \ does not work
//* is not allowed, since accountname *@example.com will not get response from LDAP server for search results are too large.
//other charaters, such as " [ ] : ; | = + ? < > / , & should be allowed as the account. There is no need to escape them.
func (la *LDAPAuth) isAccountSafe(account string) bool {
	filterMetaStr := []string{"\\", "(", ")", "!", "*"}
	for _, str := range filterMetaStr {
		if strings.Contains(account, str) {
			return false
		}
	}
	return true
}

func (la *LDAPAuth) ldapConnection() (*ldap.Conn, error) {
	glog.V(2).Infof("Dial: starting...%s", la.config.Addr)
	l, err := ldap.Dial("tcp", fmt.Sprintf("%s", la.config.Addr))
	if err != nil {
		return nil, err
	}
	if la.config.StartTLS {
		glog.V(2).Infof("StartTLS...")
		err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
		if err != nil {
			return nil, err
		}
	}
	return l, nil
}

func (la *LDAPAuth) getFilter(account string) string {
	filter := strings.NewReplacer("${account}", account).Replace(la.config.Filter)
	glog.V(2).Infof("search filter is %s", filter)
	return filter
}<commit_msg>delete account name check<commit_after>/*
   Copyright 2015 Cesanta Software Ltd.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       https://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package authn

import (
	"bytes"
	"crypto/tls"
	b64 "encoding/base64"
	"fmt"
	"strings"

	"github.com/go-ldap/ldap"
	"github.com/golang/glog"
)

type LDAPAuthConfig struct {
	Addr        string `yaml:"addr,omitempty"`
	StartTLS    bool   `yaml:"start_tls,omitempty"`
	Base        string `yaml:"base,omitempty"`
	Filter      string `yaml:"filter,omitempty"`
	Bind_DN     string `yaml:"bind_dn,omitempty"`
	Password    string `yaml:"password,omitempty"`
	GroupBaseDN string `yaml:"group_base_dn,omitempty"`
	GroupFilter string `yaml:"group_filter,omitempty"`
}

type LDAPAuth struct {
	config *LDAPAuthConfig
}

func NewLDAPAuth(c *LDAPAuthConfig) (*LDAPAuth, error) {
	return &LDAPAuth{
		config: c,
	}, nil
}

//How to authenticate user, please refer to https://github.com/go-ldap/ldap/blob/master/example_test.go#L166
func (la *LDAPAuth) Authenticate(account string, password <PASSWORD>) (bool, error) {
	if account == "" {
		return false, NoMatch
	}
	l, err := la.ldapConnection()
	if err != nil {
		return false, err
	}
	defer l.Close()

	// First bind with a read only user, to prevent the following search won't perform any write action
	if bindErr := la.bindReadOnlyUser(l); bindErr != nil {
		return false, bindErr
	}

	filter := la.getFilter(account)
	accountEntryDN, uSearchErr := la.ldapSearch(l, &la.config.Base, &filter, &[]string{})
	if uSearchErr != nil {
		return false, uSearchErr
	}
	// Bind as the user to verify their password
	if len(accountEntryDN) > 0 {
		err := l.Bind(accountEntryDN, string(password))
		if err != nil {
			return false, err
		}
	}
	// Rebind as the read only user for any futher queries
	if bindErr := la.bindReadOnlyUser(l); bindErr != nil {
		return false, bindErr
	}

	return true, nil
}

func (la *LDAPAuth) bindReadOnlyUser(l *ldap.Conn) error {
	if la.config.Bind_DN != "" {
		glog.V(2).Infof("Bind read-only user")
		sDec, _ := b64.StdEncoding.DecodeString(la.config.Password)
		err := l.Bind(la.config.Bind_DN, string(sDec))
		if err != nil {
			return err
		}
	}
	return nil
}

func (la *LDAPAuth) ldapConnection() (*ldap.Conn, error) {
	glog.V(2).Infof("Dial: starting...%s", la.config.Addr)
	l, err := ldap.Dial("tcp", fmt.Sprintf("%s", la.config.Addr))
	if err != nil {
		return nil, err
	}
	if la.config.StartTLS {
		glog.V(2).Infof("StartTLS...")
		err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
		if err != nil {
			return nil, err
		}
	}
	return l, nil
}

func (la *LDAPAuth) getFilter(account string) string {
	filter := strings.NewReplacer("${account}", account).Replace(la.config.Filter)
	glog.V(2).Infof("search filter is %s", filter)
	return filter
}
{'fraction_non_alphanumeric': 0.12533948940793047, 'fraction_numerical': 0.005975013579576317, 'mean_word_length': 3.871031746031746, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6862481', 'n_tokens_mistral': 2521, 'n_tokens_neox': 2431, 'n_words': 758}