Example #1
0
func ValidateLDAPQuery(query api.LDAPQuery) ValidationResults {
	validationResults := ValidationResults{}

	if _, err := ldap.ParseDN(query.BaseDN); err != nil {
		validationResults.AddErrors(fielderrors.NewFieldInvalid("baseDN", query.BaseDN,
			fmt.Sprintf("invalid base DN for search: %v", err)))
	}

	if len(query.Scope) > 0 {
		if _, err := ldaputil.DetermineLDAPScope(query.Scope); err != nil {
			validationResults.AddErrors(fielderrors.NewFieldInvalid("scope", query.Scope,
				"invalid LDAP search scope"))
		}
	}

	if len(query.DerefAliases) > 0 {
		if _, err := ldaputil.DetermineDerefAliasesBehavior(query.DerefAliases); err != nil {
			validationResults.AddErrors(fielderrors.NewFieldInvalid("derefAliases",
				query.DerefAliases, "LDAP alias dereferencing instruction invalid"))
		}
	}

	if query.TimeLimit < 0 {
		validationResults.AddErrors(fielderrors.NewFieldInvalid("timeout", query.TimeLimit,
			"timeout must be equal to or greater than zero"))
	}

	if _, err := ldap.CompileFilter(query.Filter); err != nil {
		validationResults.AddErrors(fielderrors.NewFieldInvalid("filter", query.Filter,
			fmt.Sprintf("invalid query filter: %v", err)))
	}

	return validationResults
}
Example #2
0
// DetermineLDAPFilter determines the LDAP search filter. Filter is a valid LDAP filter
// Default to "(objectClass=*)" per RFC
func DetermineLDAPFilter(filter string) (string, error) {
	if len(filter) == 0 {
		return defaultFilter, nil
	}
	if _, err := ldap.CompileFilter(filter); err != nil {
		return "", fmt.Errorf("invalid filter: %v", err)
	}
	return filter, nil
}
Example #3
0
// ParseURL parsed the given ldapURL as an RFC 2255 URL
// The syntax of the URL is ldap://host:port/basedn?attribute?scope?filter
func ParseURL(ldapURL string) (LDAPURL, error) {
	// Must be a valid URL to start
	parsedURL, err := url.Parse(ldapURL)
	if err != nil {
		return LDAPURL{}, err
	}

	opts := LDAPURL{}

	// Set scheme (default to ldap)
	opts.Scheme = Scheme(parsedURL.Scheme)
	switch opts.Scheme {
	case SchemeLDAP, SchemeLDAPS:
		// ok
	default:
		return LDAPURL{}, fmt.Errorf("invalid scheme %q", parsedURL.Scheme)
	}

	// Set host (default to localhost to match mod_auth_ldap)
	opts.Host = parsedURL.Host
	if len(opts.Host) == 0 {
		opts.Host = defaultHost
	}

	// Add port if needed
	if _, _, err := net.SplitHostPort(opts.Host); err != nil {
		switch opts.Scheme {
		case SchemeLDAPS:
			opts.Host = net.JoinHostPort(opts.Host, strconv.Itoa(defaultLDAPSPort))
		case SchemeLDAP:
			opts.Host = net.JoinHostPort(opts.Host, strconv.Itoa(defaultLDAPPort))
		default:
			return LDAPURL{}, fmt.Errorf("no default port for scheme %q", opts.Scheme)
		}
	}

	// Set base dn (default to "")
	// url.Parse() already percent-decodes the path
	opts.BaseDN = strings.TrimLeft(parsedURL.Path, "/")

	// Split query
	// All sections are optional
	// attribute?scope?filter?extensions
	var attributes, scope, filter, extensions string
	parts := strings.Split(parsedURL.RawQuery, "?")
	switch len(parts) {
	case 4:
		extensions = parts[3]
		fallthrough
	case 3:
		if v, err := url.QueryUnescape(parts[2]); err != nil {
			return LDAPURL{}, err
		} else {
			filter = v
		}
		fallthrough
	case 2:
		if v, err := url.QueryUnescape(parts[1]); err != nil {
			return LDAPURL{}, err
		} else {
			scope = v
		}
		fallthrough
	case 1:
		if v, err := url.QueryUnescape(parts[0]); err != nil {
			return LDAPURL{}, err
		} else {
			attributes = v
		}
	case 0:
		// no-op
	default:
		return LDAPURL{}, fmt.Errorf("too many query options %q", parsedURL.RawQuery)
	}

	// Attributes contains comma-separated attributes
	// Set query attribute to first attribute
	// Default to uid to match mod_auth_ldap
	opts.QueryAttribute = strings.Split(attributes, ",")[0]
	if len(opts.QueryAttribute) == 0 {
		opts.QueryAttribute = defaultQueryAttribute
	}

	// Scope is one of "sub", "one", or "base"
	// Default to "sub" to match mod_auth_ldap
	switch scope {
	case "", scopeWholeSubtreeString:
		opts.Scope = ScopeWholeSubtree
	case scopeSingleLevelString:
		opts.Scope = ScopeSingleLevel
	case scopeBaseObjectString:
		opts.Scope = ScopeBaseObject
	default:
		return LDAPURL{}, fmt.Errorf("invalid scope %q", scope)
	}

	// Filter is a valid LDAP filter
	// Default to "(objectClass=*)" per RFC
	opts.Filter = filter
	if len(opts.Filter) == 0 {
		opts.Filter = defaultFilter
	}
	if _, err := ldap.CompileFilter(opts.Filter); err != nil {
		return LDAPURL{}, fmt.Errorf("invalid filter: %v", err)
	}

	// Extensions are in "name=value,name2=value2" form
	// Critical extensions are prefixed with a !
	// Optional extensions are ignored, per RFC
	// Fail if there are any critical extensions, since we don't support any
	if len(extensions) > 0 {
		for _, extension := range strings.Split(extensions, ",") {
			exttype := strings.SplitN(extension, "=", 2)[0]
			if strings.HasPrefix(exttype, criticalExtensionPrefix) {
				return LDAPURL{}, fmt.Errorf("unsupported critical extension %s", extension)
			}
		}
	}

	return opts, nil

}