none
LDAP Add() RRS feed

  • Pergunta

  • Olá, escrevi o post (e já respondi) sobre LDAP/Vb.net (link - phpLdapAdmin e VB.net) onde eu abro, procuro e até troco a senha se salvo usando DirectoryEntry Class, de System.DirectoryServices.

    Uma coisa que achei que seria mais difícil foi apagar entradas no servidor LDAP, mas essa função escrevi com facilidade e funciona perfeitamente. Meu último problema: criar entradas.

    A entrada em questão será do tipo "posixAccount". Este schema deriva do "top" e do "inetOrgPerson". Eu tentei usar o método mais comum de todos que encontrei na internet, onde o método culminava em algo assim:

    Public Sub Add(ByRef Root As System.DirectoryServices.DirectoryEntry, ByRef Name As String)
        Dim Child As System.DirectoryServices.DirectoryEntry = Root.Children.Add(Name, "posixAccount")
        Child.CommigChanges()
    End Sub

    Entre o "Add(String, String)" e o "CommitChanges()" eu tentei definir as propriedades mandatórias que são:

    • "objectClass" = {CObj("posixAccount"), CObj("top"), CObj("inetOrgPerson")})
    • "loginShell" = CObj("/bin/false")
    • "givenName" = CObj("teste")
    • "sn" = CObj("outro")
    • "uidNumber" = CObj(1500)
    • "gidNumber" = CObj(500)
    • "cn" = CObj("teste outro")
    • "displayName" = CObj("teste outro")
    • "uid" = CObj("teste.outro")
    • "homeDirectory" = CObj("/home/teste.outro")
    • "userPassword" = CObj(LDAP.Connection.GenerateHash("12345678", LDAP.PasswordSecurity.SSHA))

    Sendo que a função "GenerateHash(String, PasswordSecurity)" é de minha autoria, e retorna uma String encriptada. Já usei essa função para alterar as senhas de contas existentes com sucesso.

    O "CommitChanges()" no entanto sempre retorna com erro "DirectoryServicesCOMException" (texto: "O atributo ou valor do serviço de diretório especificado já existe.").

    Já conferi, mas nenhum registro foi inserido no servidor.


    terça-feira, 22 de julho de 2014 16:55

Todas as Respostas

  • Quando não passo o atributo "objectClass" ou quando passo apenas com "posixAccount" (sem "top" e "inetOrgPerson") o erro muda o texto para: "A operação solicitada não satisfez a uma ou mais restrições associadas à classe do objeto.".

    Eu vi isso:
    http://www.codeproject.com/Articles/19689/Working-with-Active-Directory-in-VB-NET

    E isso:
    http://blogs.wrox.com/article/directory-services/

    Mas não me ajudou.


    terça-feira, 22 de julho de 2014 17:10
  • Vejam como ficou meu código até agora. Talvez mostre o que estou fazendo errado.
    Todas as funções rodam com perfeição, menos o "LDAP.Connection.Create()".

    Public Enum PasswordSecurity As Byte
        Clear = 0
        MD5 = 1
        SSHA = 2
    End Enum
    
    Public Interface CreationData
        Function Create(ByRef MainRoot As System.DirectoryServices.DirectoryEntry, ByRef MainAttribute As String, ByRef Name As String) As System.DirectoryServices.DirectoryEntry
        Function Collect(ByRef Root As System.DirectoryServices.DirectoryEntry) As Boolean
    End Interface
    
    Public Structure Credential
        Public Server As String
        Public Name As String
        Public Password As String
        Public Node As String
    
        Public Sub New(ByVal LdapServer As String, ByVal LdapUser As String, ByVal UserPassword As String, ByVal GroupNode As String)
            Me.Server = LdapServer
            Me.Name = LdapUser
            Me.Password = UserPassword
            Me.Node = GroupNode
        End Sub
    End Structure
    
    Public Module Connection
        Public Function GetRoot(ByRef User As LDAP.Credential, Optional ByRef QueryUser As Boolean = False, Optional ByRef KeepOpen As Boolean = False) As System.DirectoryServices.DirectoryEntry
            Dim ServerRoot As String = "LDAP://" + User.Server
            If QueryUser Then
                ServerRoot += "/" + User.Name
            ElseIf Not User.Node = "" Then
                ServerRoot += "/" + User.Node
            End If
            Try
                Dim Root As New System.DirectoryServices.DirectoryEntry(ServerRoot, User.Name, User.Password, System.DirectoryServices.AuthenticationTypes.None)
                Dim child As Object = Root.NativeObject
                If Not KeepOpen Then Root.Close()
                Return Root
            Catch
                Return Nothing
            End Try
        End Function
    
        Public Function GetRoot(ByRef User As LDAP.Credential, ByRef UserData As LDAP.PosixAccount) As Boolean
            Dim Root As System.DirectoryServices.DirectoryEntry = LDAP.Connection.GetRoot(User, QueryUser:=False, KeepOpen:=False)
            If Root Is Nothing Then Return False Else Return UserData.Collect(Root)
        End Function
    
        Public Function TestLogin(ByRef User As LDAP.Credential) As Boolean
            Return (LDAP.Connection.GetRoot(User, QueryUser:=True, KeepOpen:=False) IsNot Nothing)
        End Function
    
        Private Function GenerateArray(ByRef Array1() As Byte, ByRef Array2() As Byte) As Byte()
            Dim byteArrayResult(Array1.Length + Array2.Length) As Byte
            System.Array.Copy(Array1, byteArrayResult, Array1.Length)
            System.Array.Copy(Array2, 0, byteArrayResult, Array1.Length, Array2.Length)
            Return byteArrayResult
        End Function
    
        Public Function GenerateHash(ByRef NewPassword As String, ByRef SecutiryMethod As LDAP.PasswordSecurity) As String
            Select Case SecutiryMethod
                Case LDAP.PasswordSecurity.Clear : Return NewPassword
                Case LDAP.PasswordSecurity.SSHA
                    Dim rng As New System.Security.Cryptography.RNGCryptoServiceProvider()
                    Dim Salt(4) As Byte
                    rng.GetBytes(Salt)
                    Dim algorithm As New System.Security.Cryptography.SHA1Managed()
                    Dim Hash() As Byte = algorithm.ComputeHash(LDAP.Connection.GenerateArray(System.Text.Encoding.UTF8.GetBytes(NewPassword), Salt))
                    Return "{SSHA}" + System.Convert.ToBase64String(LDAP.Connection.GenerateArray(Hash, Salt))
                Case LDAP.PasswordSecurity.MD5
                    Dim algorithm As New System.Security.Cryptography.MD5CryptoServiceProvider()
                    Dim Hash() As Byte = algorithm.ComputeHash(System.Text.Encoding.ASCII.GetBytes(NewPassword))
                    Return "{MD5}" + System.Convert.ToBase64String(Hash)
                Case Else : Return ""
            End Select
        End Function
    
        Public Function Add(ByRef Root As System.DirectoryServices.DirectoryEntry, ByRef PropertyName As String, ByRef Value As Object) As Boolean
            If Root.Properties.Contains(PropertyName) Then
                Try
                    With Root.Properties.Item(PropertyName)
                        .Clear()
                        .Add(Value)
                    End With
                    Return True
                Catch
                    Return False
                End Try
            Else
                Try
                    Root.Properties.Item(PropertyName).Add(Value)
                    Return True
                Catch
                    Return False
                End Try
            End If
        End Function
    
        Public Function Add(ByRef Root As System.DirectoryServices.DirectoryEntry, ByRef PropertyName As String, ByRef Value() As Object) As Boolean
            If Root.Properties.Contains(PropertyName) Then
                Try
                    With Root.Properties.Item(PropertyName)
                        .Clear()
                        For Each Obj As Object In Value
                            .Add(Obj)
                        Next Obj
                    End With
                    Return True
                Catch
                    Return False
                End Try
            Else
                Try
                    With Root.Properties.Item(PropertyName)
                        For Each Obj As Object In Value
                            .Add(Obj)
                        Next Obj
                    End With
                    Return True
                Catch
                    Return False
                End Try
            End If
        End Function
    
        Public Function Create(ByRef User As LDAP.Credential, ByVal MainAttribute As String, ByVal Name As String, ByVal UserData As LDAP.CreationData) As Boolean
            Dim Root As System.DirectoryServices.DirectoryEntry = LDAP.Connection.GetRoot(User, QueryUser:=False, KeepOpen:=True)
            Dim mRoot As System.DirectoryServices.DirectoryEntry = UserData.Create(Root, MainAttribute, Name)
            If mRoot Is Nothing Then Return False
            Try
                Root.CommitChanges()
                mRoot.CommitChanges()
                mRoot.Close()
                Root.Close()
                Return True
            Catch
                Root.Close()
                Return False
            End Try
        End Function
    
        Public Function Delete(ByRef User As LDAP.Credential) As Boolean
            Dim mNode As String = User.Node.Substring(User.Node.IndexOf(","c) + 1)
            Dim nNode As String = User.Node.Substring(0, User.Node.IndexOf(","c))
            Dim Root As System.DirectoryServices.DirectoryEntry = LDAP.Connection.GetRoot(User, QueryUser:=False, KeepOpen:=True)
            Dim mRoot As System.DirectoryServices.DirectoryEntry = Nothing
            Try
                mRoot = Root.Children.Find(nNode)
            Catch
                Root.Close()
                Return False
            End Try
            If mRoot Is Nothing Then
                Return False
            Else
                Try
                    Root.Children.Remove(mRoot)
                    Root.CommitChanges()
                    Root.Close()
                    Return True
                Catch
                    Root.Close()
                    Return False
                End Try
            End If
        End Function
    
        Public Function ResetPassword(ByRef User As LDAP.Credential, ByVal NewPassword As String, ByRef SecutiryMethod As LDAP.PasswordSecurity) As Boolean
            Try
                Dim Root As System.DirectoryServices.DirectoryEntry = LDAP.Connection.GetRoot(User, QueryUser:=True, KeepOpen:=True)
                NewPassword = LDAP.Connection.GenerateHash(NewPassword, SecutiryMethod)
                Root.Properties.Item("userPassword").Clear()
                Root.Properties.Item("userPassword").Add(System.Text.Encoding.UTF8.GetBytes(NewPassword))
                Root.CommitChanges()
                Return True
            Catch
                Return False
            End Try
        End Function
    End Module
    
    Public Structure PosixAccount
        Implements LDAP.CreationData
        Public Const mainclass As String = "posixAccount"
        Public Const subclass As String = "top"
        Public Const baseclass As String = "inetOrgPerson"
        Public givenName As String
        Public sn As String
        Public uid As String
        Public uidNumber As Integer
        Public gidNumber As Integer
        Public userPassword As String
    
        Public Function Create(ByRef Root As System.DirectoryServices.DirectoryEntry, ByRef MainAttribute As String, ByRef Name As String) As System.DirectoryServices.DirectoryEntry Implements LDAP.CreationData.Create
            Dim mRoot As System.DirectoryServices.DirectoryEntry = Root.Children.Add(MainAttribute + "=" + Name, LDAP.PosixAccount.mainclass)
            mRoot.AuthenticationType = System.DirectoryServices.AuthenticationTypes.None
            If Not LDAP.Connection.Add(mRoot, "givenName", CObj(Me.givenName)) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "cn", CObj(Me.givenName + " " + Me.sn)) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "gidNumber", CObj(Me.gidNumber)) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "homeDirectory", CObj("/home/" + Me.uid)) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "sn", CObj(Me.sn)) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "objectClass", {CObj(LDAP.PosixAccount.baseclass), CObj(LDAP.PosixAccount.subclass), CObj(LDAP.PosixAccount.mainclass)}) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "uidNumber", CObj(Me.uidNumber)) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "uid", CObj(Me.uid)) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "loginShell", CObj("/bin/false")) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "displayName", CObj(Me.givenName + " " + Me.sn)) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "userPassword", CObj(LDAP.Connection.GenerateHash("12345678", LDAP.PasswordSecurity.SSHA))) Then Return Nothing
            Return mRoot
        End Function
    
        Public Function Collect(ByRef Root As System.DirectoryServices.DirectoryEntry) As Boolean Implements LDAP.CreationData.Collect
            Try
                Me.givenName = Root.Properties.Item("givenName").Value.ToString()
                Me.sn = Root.Properties.Item("sn").Value.ToString()
                Me.uid = Root.Properties.Item("uid").Value.ToString()
                Me.userPassword = System.Text.Encoding.UTF8.GetString(CType(Root.Properties.Item("userPassword").Value, Byte()))
                Me.uidNumber = CInt(Root.Properties.Item("uidNumber").Value)
                Me.gidNumber = CInt(Root.Properties.Item("gidNumber").Value)
                Return True
            Catch
                Return False
            End Try
        End Function
    
        Public Sub New(ByRef FirstName As String, ByRef LastName As String, ByRef UserName As String, ByRef Password As String, ByRef UserNumber As Integer, ByRef GroupNumber As Integer)
            Me.givenName = FirstName
            Me.sn = LastName
            Me.uid = UserName
            Me.userPassword = Password
            Me.uidNumber = UserNumber
            Me.gidNumber = GroupNumber
        End Sub
    End Structure
    
    Friend Module Console
        Friend Sub Main(ByVal Arg() As String)
            Dim B As Boolean = True
            B = LDAP.Connection.Create(New LDAP.Credential("servidor.minhaempresa.com.br", "cn=admin,dc=minhaempresa,dc=com,dc=br", "senhadoadmin", "ou=people,dc=minhaempresa,dc=com,dc=br"), "uid", "teste.outro", New LDAP.PosixAccount("teste", "outro", "teste.outro", "abc.123", 3000, 500))
            If B Then B = LDAP.Connection.Delete(New LDAP.Credential("servidor.minhaempresa.com.br", "cn=admin,dc=minhaempresa,dc=com,dc=br", "senhadoadmin", "uid=teste.outro,ou=people,dc=minhaempresa,dc=com,dc=br"))
            System.Console.WriteLine(My.Application.Info.ProductName + " for .Net 2.0.")
            System.Console.WriteLine("Tools for Web Application use only!")
            System.Console.WriteLine()
            System.Console.WriteLine("Done! Press any key to terminate.")
            System.Console.Read()
        End Sub
    End Module

    O projeto se chama LDAP, que é o namespace raiz. Para fins de exemplo, o "servidor.minhaempresa.com.br" é o endereço (DNS) do meu servidor, que atende em um IP vinculado a ele. "dc=minhaempresa,dc=com,dc=br" é o nó raiz do servidor LDAP, e "ou=people" é o "organizationalUnit" ao qual estou tentando adicionar o nó filho (chamado de "uid=teste.outro"). O parâmetro 500 (que passo no novo registro é o valor da propriedade "gidNumber" que é o "uidNumber" de "cn=funcionarios,dc=minhaempresa,dc=com,dc=br" - que é um atributo mandatório do "posixAccount".


    terça-feira, 22 de julho de 2014 19:51
  • Notei algo. Eu consigo criar um "inetOrgPerson".

    O schema "inetOrgPerson" diz que é obrigatório o preenchimento do campo "cn" e "sn". Este deriva da classe "top" - que tem que ter o atributo "objectClass".

    Ao usar então "sn=outro", "cn=teste outro" e "objectClass=inetOrgPerson" (note que eu não coloco a classe "top" na lista de classes) ele cria o objeto com sucesso.

    Fui então ver os atributos do "posixAccount". Esse deriva apenas da classe "top". Os atributos obrigatórios são "cn", "gidNumber", "homeDirectory", "uid" e "uidNumber". Adicionando o "objectClass" (= "posixAccount") e tentando salvar ele me dá erro.

    Não entendo a lógica. Alguém sabe?

    quarta-feira, 23 de julho de 2014 12:08
  • Ok, fiz alterações na interface "creationData" e a estrutura "posixAccount" para isso:

    Public Interface CreationData
        ReadOnly Property BaseAttribute(ByVal GetValue As Boolean) As String
        Function Create(ByRef MainRoot As System.DirectoryServices.DirectoryEntry) As System.DirectoryServices.DirectoryEntry
        Function Apply(ByRef Root As System.DirectoryServices.DirectoryEntry) As Boolean
        Function Collect(ByRef Root As System.DirectoryServices.DirectoryEntry) As Boolean
    End Interface
    
    Public Structure PosixAccount
        Implements LDAP.CreationData
        Public Const mainclass As String = "posixAccount"
        Public Const subclass As String = "top"
        Public Const baseclass As String = "inetOrgPerson"
        Public givenName As String
        Public sn As String
        Public uid As String
        Public uidNumber As Integer
        Public gidNumber As Integer
        Public userPassword As String
    
        Public ReadOnly Property BaseAttribute(ByVal GetValue As Boolean) As String Implements LDAP.CreationData.BaseAttribute
            Get
                If GetValue Then Return (Me.givenName + " " + Me.sn) Else Return "cn"
            End Get
        End Property
    
        Public Function Create(ByRef MainRoot As System.DirectoryServices.DirectoryEntry) As System.DirectoryServices.DirectoryEntry Implements LDAP.CreationData.Create
            Dim mRoot As System.DirectoryServices.DirectoryEntry = MainRoot.Children.Add(Me.BaseAttribute(False) + "=" + Me.BaseAttribute(True), LDAP.PosixAccount.baseclass)
            mRoot.AuthenticationType = System.DirectoryServices.AuthenticationTypes.None
            If Not LDAP.Connection.Add(mRoot, "cn", CObj(Me.givenName + " " + Me.sn)) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "sn", CObj(Me.sn)) Then Return Nothing
            If Not LDAP.Connection.Add(mRoot, "objectClass", {CObj(LDAP.PosixAccount.baseclass)}) Then Return Nothing
            Return mRoot
        End Function
    
        Public Function Apply(ByRef Root As System.DirectoryServices.DirectoryEntry) As Boolean Implements LDAP.CreationData.Apply
            If Not LDAP.Connection.Add(Root, "givenName", CObj(Me.givenName)) Then Return False
            If Not LDAP.Connection.Add(Root, "gidNumber", CObj(Me.gidNumber)) Then Return False
            If Not LDAP.Connection.Add(Root, "homeDirectory", CObj("/home/" + Me.uid)) Then Return False
            If Not LDAP.Connection.Add(Root, "objectClass", {CObj(LDAP.PosixAccount.baseclass), CObj(LDAP.PosixAccount.subclass), CObj(LDAP.PosixAccount.mainclass)}) Then Return False
            If Not LDAP.Connection.Add(Root, "uidNumber", CObj(Me.uidNumber)) Then Return False
            If Not LDAP.Connection.Add(Root, "uid", CObj(Me.uid)) Then Return False
            If Not LDAP.Connection.Add(Root, "loginShell", CObj("/bin/false")) Then Return False
            If Not LDAP.Connection.Add(Root, "displayName", CObj(Me.givenName + " " + Me.sn)) Then Return False
            If Not LDAP.Connection.Add(Root, "userPassword", CObj(LDAP.Connection.GenerateHash("12345678", LDAP.PasswordSecurity.SSHA))) Then Return False
            Return True
        End Function
    
        Public Function Collect(ByRef Root As System.DirectoryServices.DirectoryEntry) As Boolean Implements LDAP.CreationData.Collect
            Try
                Me.givenName = Root.Properties.Item("givenName").Value.ToString()
                Me.sn = Root.Properties.Item("sn").Value.ToString()
                Me.uid = Root.Properties.Item("uid").Value.ToString()
                Me.userPassword = System.Text.Encoding.UTF8.GetString(CType(Root.Properties.Item("userPassword").Value, Byte()))
                Me.uidNumber = CInt(Root.Properties.Item("uidNumber").Value)
                Me.gidNumber = CInt(Root.Properties.Item("gidNumber").Value)
                Return True
            Catch
                Return False
            End Try
        End Function
    
        Public Sub New(ByRef FirstName As String, ByRef LastName As String, ByRef UserName As String, ByRef Password As String, ByRef UserNumber As Integer, ByRef GroupNumber As Integer)
            Me.givenName = FirstName
            Me.sn = LastName
            Me.uid = UserName
            Me.userPassword = Password
            Me.uidNumber = UserNumber
            Me.gidNumber = GroupNumber
        End Sub
    End Structure

    E alterei a função "Create" para isso:

    Public Function Create(ByRef User As LDAP.Credential, ByVal MainAttribute As String, ByVal Name As String, ByVal UserData As LDAP.CreationData) As Boolean
            Dim Root As System.DirectoryServices.DirectoryEntry = LDAP.Connection.GetRoot(User, QueryUser:=False, KeepOpen:=True)
            If Root Is Nothing Then Return False
            Dim mRoot As System.DirectoryServices.DirectoryEntry = UserData.Create(Root)
            If mRoot IsNot Nothing Then
                'Try
                Dim newCredential As New LDAP.Credential(User.Server, User.Name, User.Password, mRoot.Name + "," + User.Node)
                mRoot.CommitChanges()
                'Try
                If UserData.Apply(mRoot) Then
                    mRoot.CommitChanges()
                    mRoot.Close()
                    mRoot = LDAP.Connection.GetRoot(newCredential, KeepOpen:=True)
                    mRoot.Rename(MainAttribute + "=" + Name + "," + User.Node)
                    mRoot.CommitChanges()
                    mRoot.Close()
                    Root.Close()
                    Return True
                Else
                    mRoot.Close()
                    LDAP.Connection.Delete(newCredential)
                End If
                'Catch
                'mRoot.Close()
                'LDAP.Connection.Delete(newCredential)
                'End Try
                'Catch
                'mRoot.Close()
                'End Try
            End If
            Root.Close()
            Return False
        End Function

    Notei que passou a funcionar até o "Rename()". Ou seja, ele cria primeiro o "inetOrgPerson" com 3 atributos e salva. Depois adiciono as outras classes "top" e "posixAccount" e altero os atributos e salvo. Até ai tudo bem.

    O problema é no rename, onde tento renomear "cn=teste outro" (do nó "ou=people,dc=minhaempresa,dc=com,dc=br") para "uid=teste.outro". Note que a alteração deve mudar também o atributo principal.

    Se eu faço isso no administrador do LDAP, funciona perfeitamente. via código dá erro.

    quarta-feira, 23 de julho de 2014 13:11
  • Ajuda por favor, só preciso corrigir o "Rename()" de "cn=Teste Outro" para "uid=teste.outro".
    sexta-feira, 25 de julho de 2014 10:29
  • Gente, ajuda... alguém?
    segunda-feira, 28 de julho de 2014 02:10
  • Notei outro problema, o "Root.Rename(newName)" ("Root" sendo um System.DirectoryServices.DirectoryEntry) da o erro "A operação solicitada não satisfez a uma ou mais restrições associadas à classe do objeto." mesmo se eu manter o mesmo atributo "cn".

    Alguém me ajude por favor.

    segunda-feira, 28 de julho de 2014 11:28
  • Postei mais um pedido de ajuda, agora no StackOverflow... rezando para ter alguma resposta em algum lugar... daqui a pouco só ligando pro Bill Gates.

    Veja o link: http://stackoverflow.com/questions/24997558/directoryentry-rename-on-ldap-server

    segunda-feira, 28 de julho de 2014 15:02
  • Fui abandonado ... hehehe
    quarta-feira, 6 de agosto de 2014 14:08