Search in sources :

Example 1 with LdapEntry

use of org.graylog2.shared.security.ldap.LdapEntry in project graylog2-server by Graylog2.

the class LdapUserAuthenticator method updateFromLdap.

private void updateFromLdap(User user, LdapEntry userEntry, LdapSettings ldapSettings, String username) {
    final String displayNameAttribute = ldapSettings.getDisplayNameAttribute();
    final String fullName = firstNonNull(userEntry.get(displayNameAttribute), username);
    user.setName(username);
    user.setFullName(fullName);
    user.setExternal(true);
    if (user.getTimeZone() == null) {
        user.setTimeZone(rootTimeZone);
    }
    final String email = userEntry.getEmail();
    if (isNullOrEmpty(email)) {
        LOG.debug("No email address found for user {} in LDAP. Using {}@localhost", username, username);
        user.setEmail(username + "@localhost");
    } else {
        user.setEmail(email);
    }
    // TODO This is a crude hack until we have a proper way to distinguish LDAP users from normal users
    if (isNullOrEmpty(user.getHashedPassword())) {
        ((UserImpl) user).setHashedPassword("User synced from LDAP.");
    }
    // map ldap groups to user roles, if the mapping is present
    final Set<String> translatedRoleIds = Sets.newHashSet(Sets.union(Sets.newHashSet(ldapSettings.getDefaultGroupId()), ldapSettings.getAdditionalDefaultGroupIds()));
    if (!userEntry.getGroups().isEmpty()) {
        // ldap search returned groups, these always override the ones set on the user
        try {
            final Map<String, Role> roleNameToRole = roleService.loadAllLowercaseNameMap();
            for (String ldapGroupName : userEntry.getGroups()) {
                final String roleName = ldapSettings.getGroupMapping().get(ldapGroupName);
                if (roleName == null) {
                    LOG.debug("User {}: No group mapping for ldap group <{}>", username, ldapGroupName);
                    continue;
                }
                final Role role = roleNameToRole.get(roleName.toLowerCase(Locale.ENGLISH));
                if (role != null) {
                    LOG.debug("User {}: Mapping ldap group <{}> to role <{}>", username, ldapGroupName, role.getName());
                    translatedRoleIds.add(role.getId());
                } else {
                    LOG.warn("User {}: No role found for ldap group <{}>", username, ldapGroupName);
                }
            }
        } catch (NotFoundException e) {
            LOG.error("Unable to load user roles", e);
        }
    } else if (ldapSettings.getGroupMapping().isEmpty() || ldapSettings.getGroupSearchBase().isEmpty() || ldapSettings.getGroupSearchPattern().isEmpty() || ldapSettings.getGroupIdAttribute().isEmpty()) {
        // no group mapping or configuration set, we'll leave the previously set groups alone on sync
        // when first creating the user these will be empty
        translatedRoleIds.addAll(user.getRoleIds());
    }
    user.setRoleIds(translatedRoleIds);
    // preserve the raw permissions (the ones without the synthetic self-edit permissions or the "*" admin one)
    user.setPermissions(user.getPermissions());
}
Also used : Role(org.graylog2.shared.users.Role) UserImpl(org.graylog2.users.UserImpl) NotFoundException(org.graylog2.database.NotFoundException)

Example 2 with LdapEntry

use of org.graylog2.shared.security.ldap.LdapEntry in project graylog2-server by Graylog2.

the class LdapResource method testLdapConfiguration.

@POST
@Timed
@RequiresPermissions(RestPermissions.LDAP_EDIT)
@ApiOperation("Test LDAP Configuration")
@Path("/test")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@NoAuditEvent("only used to test LDAP configuration")
public LdapTestConfigResponse testLdapConfiguration(@ApiParam(name = "Configuration to test", required = true) @Valid @NotNull LdapTestConfigRequest request) {
    final LdapConnectionConfig config = new LdapConnectionConfig();
    final URI ldapUri = request.ldapUri();
    config.setLdapHost(ldapUri.getHost());
    config.setLdapPort(ldapUri.getPort());
    config.setUseSsl(ldapUri.getScheme().startsWith("ldaps"));
    config.setUseTls(request.useStartTls());
    if (request.trustAllCertificates()) {
        config.setTrustManagers(new TrustAllX509TrustManager());
    }
    if (!isNullOrEmpty(request.systemUsername()) && !isNullOrEmpty(request.systemPassword())) {
        config.setName(request.systemUsername());
        config.setCredentials(request.systemPassword());
    }
    LdapNetworkConnection connection = null;
    try {
        try {
            connection = ldapConnector.connect(config);
        } catch (LdapException e) {
            return LdapTestConfigResponse.create(false, false, false, Collections.<String, String>emptyMap(), Collections.<String>emptySet(), e.getMessage());
        }
        if (null == connection) {
            return LdapTestConfigResponse.create(false, false, false, Collections.<String, String>emptyMap(), Collections.<String>emptySet(), "Could not connect to LDAP server");
        }
        boolean connected = connection.isConnected();
        boolean systemAuthenticated = connection.isAuthenticated();
        // the web interface allows testing the connection only, in that case we can bail out early.
        if (request.testConnectOnly()) {
            return LdapTestConfigResponse.create(connected, systemAuthenticated, false, Collections.<String, String>emptyMap(), Collections.<String>emptySet());
        }
        String userPrincipalName = null;
        boolean loginAuthenticated = false;
        Map<String, String> entryMap = Collections.emptyMap();
        String exception = null;
        Set<String> groups = Collections.emptySet();
        try {
            final LdapEntry entry = ldapConnector.search(connection, request.searchBase(), request.searchPattern(), "*", request.principal(), request.activeDirectory(), request.groupSearchBase(), request.groupIdAttribute(), request.groupSearchPattern());
            if (entry != null) {
                userPrincipalName = entry.getBindPrincipal();
                entryMap = entry.getAttributes();
                groups = entry.getGroups();
            }
        } catch (CursorException | LdapException e) {
            exception = e.getMessage();
        }
        try {
            loginAuthenticated = ldapConnector.authenticate(connection, userPrincipalName, request.password());
        } catch (Exception e) {
            exception = e.getMessage();
        }
        return LdapTestConfigResponse.create(connected, systemAuthenticated, loginAuthenticated, entryMap, groups, exception);
    } finally {
        if (connection != null) {
            try {
                connection.close();
            } catch (IOException e) {
                LOG.warn("Unable to close LDAP connection.", e);
            }
        }
    }
}
Also used : LdapConnectionConfig(org.apache.directory.ldap.client.api.LdapConnectionConfig) LdapEntry(org.graylog2.shared.security.ldap.LdapEntry) LdapNetworkConnection(org.apache.directory.ldap.client.api.LdapNetworkConnection) IOException(java.io.IOException) TrustAllX509TrustManager(org.graylog2.security.TrustAllX509TrustManager) URI(java.net.URI) BadRequestException(javax.ws.rs.BadRequestException) InternalServerErrorException(javax.ws.rs.InternalServerErrorException) CursorException(org.apache.directory.api.ldap.model.cursor.CursorException) IOException(java.io.IOException) ValidationException(org.graylog2.plugin.database.ValidationException) LdapException(org.apache.directory.api.ldap.model.exception.LdapException) CursorException(org.apache.directory.api.ldap.model.cursor.CursorException) LdapException(org.apache.directory.api.ldap.model.exception.LdapException) Path(javax.ws.rs.Path) RequiresPermissions(org.apache.shiro.authz.annotation.RequiresPermissions) POST(javax.ws.rs.POST) Consumes(javax.ws.rs.Consumes) Produces(javax.ws.rs.Produces) Timed(com.codahale.metrics.annotation.Timed) ApiOperation(io.swagger.annotations.ApiOperation) NoAuditEvent(org.graylog2.audit.jersey.NoAuditEvent)

Example 3 with LdapEntry

use of org.graylog2.shared.security.ldap.LdapEntry in project graylog2-server by Graylog2.

the class LdapConnector method search.

@Nullable
public LdapEntry search(LdapNetworkConnection connection, String searchBase, String searchPattern, String displayNameAttribute, String principal, boolean activeDirectory, String groupSearchBase, String groupIdAttribute, String groupSearchPattern) throws LdapException, CursorException {
    final LdapEntry ldapEntry = new LdapEntry();
    final Set<String> groupDns = Sets.newHashSet();
    final String filter = new MessageFormat(searchPattern, Locale.ENGLISH).format(new Object[] { sanitizePrincipal(principal) });
    if (LOG.isTraceEnabled()) {
        LOG.trace("Search {} for {}, starting at {}", activeDirectory ? "ActiveDirectory" : "LDAP", filter, searchBase);
    }
    try (final EntryCursor entryCursor = connection.search(searchBase, filter, SearchScope.SUBTREE, groupIdAttribute, displayNameAttribute, "dn", "uid", "userPrincipalName", "mail", "rfc822Mailbox", "memberOf", "isMemberOf")) {
        final Iterator<Entry> it = entryCursor.iterator();
        if (it.hasNext()) {
            final Entry e = it.next();
            // always set the proper DN for the entry, we need it for group matching
            ldapEntry.setDn(e.getDn().getName());
            // for generic LDAP use the dn of the entry for the subsequent bind, active directory needs the userPrincipalName attribute (set below)
            if (!activeDirectory) {
                ldapEntry.setBindPrincipal(e.getDn().getName());
            }
            for (Attribute attribute : e.getAttributes()) {
                if (activeDirectory && "userPrincipalName".equalsIgnoreCase(attribute.getId())) {
                    ldapEntry.setBindPrincipal(attribute.getString());
                }
                if (attribute.isHumanReadable()) {
                    ldapEntry.put(attribute.getId(), Joiner.on(", ").join(attribute.iterator()));
                }
                // ActiveDirectory (memberOf) and Sun Directory Server (isMemberOf)
                if ("memberOf".equalsIgnoreCase(attribute.getId()) || "isMemberOf".equalsIgnoreCase(attribute.getId())) {
                    for (Value<?> group : attribute) {
                        groupDns.add(group.getString());
                    }
                }
            }
        } else {
            LOG.trace("No LDAP entry found for filter {}", filter);
            return null;
        }
        if (!groupDns.isEmpty() && !isNullOrEmpty(groupSearchBase) && !isNullOrEmpty(groupIdAttribute)) {
            // according to groupIdAttribute if present
            try {
                for (String groupDn : groupDns) {
                    LOG.trace("Looking up group {}", groupDn);
                    try {
                        Entry group = connection.lookup(groupDn, groupIdAttribute);
                        // See: https://github.com/Graylog2/graylog2-server/issues/1453
                        if (group != null) {
                            final Attribute groupId = group.get(groupIdAttribute);
                            LOG.trace("Resolved {} to group {}", groupDn, groupId);
                            if (groupId != null) {
                                final String string = groupId.getString();
                                ldapEntry.addGroups(Collections.singleton(string));
                            }
                        } else {
                            LOG.debug("Unable to lookup group: {}", groupDn);
                        }
                    } catch (LdapException e) {
                        LOG.warn("Error while looking up group " + groupDn, e);
                    }
                }
            } catch (Exception e) {
                LOG.error("Unexpected error during LDAP group resolution", e);
            }
        }
        if (ldapEntry.getGroups().isEmpty() && !isNullOrEmpty(groupSearchBase) && !isNullOrEmpty(groupIdAttribute) && !isNullOrEmpty(groupSearchPattern)) {
            ldapEntry.addGroups(findGroups(connection, groupSearchBase, groupSearchPattern, groupIdAttribute, ldapEntry));
            LOG.trace("LDAP search found entry for DN {} with search filter {}: {}", ldapEntry.getDn(), filter, ldapEntry);
        } else {
            if (groupDns.isEmpty()) {
                LOG.info("LDAP group search base, id attribute or object class missing, not iterating over LDAP groups.");
            }
        }
        return ldapEntry;
    } catch (IOException e) {
        LOG.debug("Error while closing cursor", e);
        return null;
    }
}
Also used : EntryCursor(org.apache.directory.api.ldap.model.cursor.EntryCursor) Entry(org.apache.directory.api.ldap.model.entry.Entry) LdapEntry(org.graylog2.shared.security.ldap.LdapEntry) MessageFormat(java.text.MessageFormat) Attribute(org.apache.directory.api.ldap.model.entry.Attribute) LdapEntry(org.graylog2.shared.security.ldap.LdapEntry) IOException(java.io.IOException) LdapException(org.apache.directory.api.ldap.model.exception.LdapException) CursorException(org.apache.directory.api.ldap.model.cursor.CursorException) UncheckedTimeoutException(com.google.common.util.concurrent.UncheckedTimeoutException) IOException(java.io.IOException) LdapInvalidDnException(org.apache.directory.api.ldap.model.exception.LdapInvalidDnException) LdapException(org.apache.directory.api.ldap.model.exception.LdapException) Nullable(javax.annotation.Nullable)

Example 4 with LdapEntry

use of org.graylog2.shared.security.ldap.LdapEntry in project graylog2-server by Graylog2.

the class LdapConnector method findGroups.

public Set<String> findGroups(LdapNetworkConnection connection, String groupSearchBase, String groupSearchPattern, String groupIdAttribute, @Nullable LdapEntry ldapEntry) {
    final Set<String> groups = Sets.newHashSet();
    try (final EntryCursor groupSearch = connection.search(groupSearchBase, groupSearchPattern, SearchScope.SUBTREE, "objectClass", ATTRIBUTE_UNIQUE_MEMBER, ATTRIBUTE_MEMBER, ATTRIBUTE_MEMBER_UID, groupIdAttribute)) {
        LOG.trace("LDAP search for groups: {} starting at {}", groupSearchPattern, groupSearchBase);
        for (Entry e : groupSearch) {
            if (LOG.isTraceEnabled()) {
                LOG.trace("Group Entry: {}", e.toString("  "));
            }
            if (!e.containsAttribute(groupIdAttribute)) {
                LOG.warn("Unknown group id attribute {}, skipping group entry {}", groupIdAttribute, e);
                continue;
            }
            final String groupId = e.get(groupIdAttribute).getString();
            if (ldapEntry == null) {
                // no membership lookup possible (we have no user), simply collect the found group names
                groups.add(groupId);
            } else {
                // test if the given dn parameter is actually member of any of the found groups
                String memberAttribute;
                if (e.hasObjectClass("groupOfUniqueNames")) {
                    memberAttribute = ATTRIBUTE_UNIQUE_MEMBER;
                } else if (e.hasObjectClass("groupOfNames") || e.hasObjectClass("group")) {
                    memberAttribute = ATTRIBUTE_MEMBER;
                } else if (e.hasObjectClass("posixGroup")) {
                    memberAttribute = ATTRIBUTE_MEMBER_UID;
                } else {
                    // Trying auto detection of the member attribute. This should be configurable!
                    if (e.containsAttribute(ATTRIBUTE_UNIQUE_MEMBER)) {
                        memberAttribute = ATTRIBUTE_UNIQUE_MEMBER;
                    } else if (e.containsAttribute(ATTRIBUTE_MEMBER_UID)) {
                        memberAttribute = ATTRIBUTE_MEMBER_UID;
                    } else {
                        memberAttribute = ATTRIBUTE_MEMBER;
                    }
                    LOG.warn("Unable to auto-detect the LDAP group object class, assuming '{}' is the correct attribute.", memberAttribute);
                }
                final Attribute members = e.get(memberAttribute);
                if (members != null) {
                    final String dn = normalizedDn(ldapEntry.getDn());
                    final String uid = ldapEntry.get("uid");
                    for (Value<?> member : members) {
                        LOG.trace("DN {} == {} member?", dn, member.getString());
                        if (dn != null && dn.equalsIgnoreCase(normalizedDn(member.getString()))) {
                            groups.add(groupId);
                        } else {
                            // check against the uid attribute of the user.
                            if (!isNullOrEmpty(uid) && uid.equalsIgnoreCase(member.getString())) {
                                LOG.trace("UID {} == {} member?", uid, member.getString());
                                groups.add(groupId);
                            }
                        }
                    }
                }
            }
        }
    } catch (Exception e) {
        LOG.warn("Unable to iterate over user's groups, unable to perform group mapping. Graylog does not support " + "LDAP referrals at the moment. Please see " + DocsHelper.PAGE_LDAP_TROUBLESHOOTING.toString() + " for more information.", ExceptionUtils.getRootCause(e));
    }
    return groups;
}
Also used : EntryCursor(org.apache.directory.api.ldap.model.cursor.EntryCursor) Entry(org.apache.directory.api.ldap.model.entry.Entry) LdapEntry(org.graylog2.shared.security.ldap.LdapEntry) Attribute(org.apache.directory.api.ldap.model.entry.Attribute) CursorException(org.apache.directory.api.ldap.model.cursor.CursorException) UncheckedTimeoutException(com.google.common.util.concurrent.UncheckedTimeoutException) IOException(java.io.IOException) LdapInvalidDnException(org.apache.directory.api.ldap.model.exception.LdapInvalidDnException) LdapException(org.apache.directory.api.ldap.model.exception.LdapException)

Example 5 with LdapEntry

use of org.graylog2.shared.security.ldap.LdapEntry in project graylog2-server by Graylog2.

the class LdapConnectorTest method testAllGroupClassesLookup.

@Test
public void testAllGroupClassesLookup() throws Exception {
    final LdapEntry entry = connector.search(connection, "ou=users,dc=example,dc=com", "(&(objectClass=posixAccount)(uid={0}))", "cn", "john", false, "ou=groups,dc=example,dc=com", "cn", "(|(objectClass=posixGroup)(objectClass=groupOfNames)(objectclass=groupOfUniqueNames))");
    assertThat(entry).isNotNull();
    assertThat(entry.getDn()).isNotNull().isEqualTo("cn=John Doe,ou=users,dc=example,dc=com");
    assertThat(entry.getGroups()).hasSize(4).contains("Developers", "QA", "Engineers", "Whitespace Engineers");
}
Also used : LdapEntry(org.graylog2.shared.security.ldap.LdapEntry) Test(org.junit.Test)

Aggregations

LdapEntry (org.graylog2.shared.security.ldap.LdapEntry)12 Test (org.junit.Test)8 User (org.graylog2.plugin.database.users.User)4 IOException (java.io.IOException)3 CursorException (org.apache.directory.api.ldap.model.cursor.CursorException)3 LdapException (org.apache.directory.api.ldap.model.exception.LdapException)3 ValidationException (org.graylog2.plugin.database.ValidationException)3 TrustAllX509TrustManager (org.graylog2.security.TrustAllX509TrustManager)3 LdapSettings (org.graylog2.shared.security.ldap.LdapSettings)3 UserImpl (org.graylog2.users.UserImpl)3 UncheckedTimeoutException (com.google.common.util.concurrent.UncheckedTimeoutException)2 UsingDataSet (com.lordofthejars.nosqlunit.annotation.UsingDataSet)2 MessageFormat (java.text.MessageFormat)2 EntryCursor (org.apache.directory.api.ldap.model.cursor.EntryCursor)2 Attribute (org.apache.directory.api.ldap.model.entry.Attribute)2 Entry (org.apache.directory.api.ldap.model.entry.Entry)2 LdapInvalidDnException (org.apache.directory.api.ldap.model.exception.LdapInvalidDnException)2 Permissions (org.graylog2.shared.security.Permissions)2 Timed (com.codahale.metrics.annotation.Timed)1 VisibleForTesting (com.google.common.annotations.VisibleForTesting)1