net-ldap create user with password - ruby

I am trying to create an AD account with a password already set using the net-ldap gem. I am able to connect to the server fine. And I am also able to add a new user without passing the :unicodepwd attribute however when the new user is created there is no password set. When I do pass that attribute the user is not created and it fails with error code 53 and the following message Unwilling to perform. I also get the same error if I try to replace the password of the user after I have created it. I've come across many potential answers but none of them have worked for me.
def initialize
#client = Net::LDAP.new
#client.host = server_ip
#client.base = base
#client.port = 389
#client.auth(username, password)
if #client.bind
puts "Connected"
add("TEST", "JEST", "testjest")
else
puts "Not Connected"
display_error
end
end
def add(first_name, last_name, username)
dn = dn_value
attrs = {
:objectclass => ["top", "person", "organizationalPerson", "user"],
:cn => fullname(first_name, last_name),
:sn => last_name.capitalize,
:givenname => first_name.capitalize,
:displayname => fullname(first_name, last_name),
:name => fullname(first_name, last_name),
:samaccountname => username,
:unicodePwd => '"password"'.encode("utf-16")
}
#client.add(:dn => dn, :attributes => attrs)
if #client.get_operation_result.code != 0
puts "Failed to add user #{fullname(first_name, last_name)}"
display_error
else
puts "Added user #{fullname(first_name, last_name)}"
end
end
How would I set a password for the user when I create the user and not have to access it through the gui in order to update the password? Any help is appreciated
Thanks
UPDATE
I was able to get this to work once I encoded the string in a different way and connected to the SSL port 636 rather than default port 389. Using encode was the issue, seems like it was incorrectly encoding the password.
This is my new connection
#client = Net::LDAP.new
#client.host = server_ip
#client.base = base
#client.port = 636
#client.encryption(:method => :simple_tls)
#client.auth(username, password)
And the method which i used to encode the password
def encode_passwd(string)
newstring = ""
string = "\"" + string + "\""
string.split("").each do |c|
newstring = "#{newstring}#{c}\000"
end
return newstring
end
Hope this helps someone in the future

The Net::LDAP::Password.generate does not work with ActiveDirectory. The :unicodePwd LDAP-Entry-Attribute (speaking ruby-gem net-ldap parlance), you
have to encode it like this
unicodepwd = "\"#{plain_text_password}\"".encode(Encoding::UTF_16LE).force_encoding(Encoding::ASCII_8BIT)
See details about the encoding here: https://msdn.microsoft.com/en-us/library/cc223248.aspx

I just found out there is already a password generation function included in Net::LDAP !
Net::LDAP::Password.generate(:md5, 'yourPlaintextPass')
Documentation here

Related

Ruby update all same object data in hash array for JSON.parse

So a = first is
=> <Ng::EntityConfiguration id: 15903, entity_id: 1, entity_type: "Ng::Company", key: "wpa2.psk", value: "[{"ssid":"Main-Hall-Staff","password":"abc123","dhcp":"Enabled"},{"ssid":"Main-Hall-Guest","password":"def456","dhcp":"Disabled"}]", created_at: "2016-11-08 11:03:51", updated_at: "2016-11-08 11:03:51", name: "WIFI/Main Hall">
I have a.value which is will return:
"[
{\"ssid\":\"Main-Hall-Staff\",\"password\":\"abc123\"},
{\"ssid\":\"Main-Hall-Guest\",\"password\":\"def456\"}
]"
My question is, how to update both password value and save it?
new_pass1 = 'xyz123'
new_pass2 = 'xyz321'
I have tried code (below) but this will only update first password if i only have one hash_array.
Here is my full code
def encrypt_pass
# get the actual password
parse = JSON.parse(self.value)
get_pass = parse.last['password']
# encrypt the password
crypt = ActiveSupport::MessageEncryptor.new(ENV["SECRET_KEY_BASE"])
encrypted = crypt.encrypt_and_sign(get_pass)
# save the new encrypted password
parse.first['password'] = encrypted
encrypt_pass = parse.to_json
self.value = encrypt_pass
end
Just to be clear, you're trying to update both the Main-Hall-Staff password and the Main-Hall-Guest password (all passwords) from your record to be the encrypted version of themselves? I'm assuming this method is called in a before_save callback of some sort? If you show more code related to the model I can give you more details.
def encrypt_pass
# Changed the name to devises, always use easy to understand naming
# Also rescuing from a JSON parse error, this isnt always recommended
# as it hides other errors that might be unrelated to parsing
devices = JSON.parse(self.value) rescue []
crypt = ActiveSupport::MessageEncryptor.new(ENV["SECRET_KEY_BASE"])
devices.each do |device|
# get the actual password
password = device['password']
# encrypt the password
encrypted_pass = crypt.encrypt_and_sign(password)
# Save the encrypted password
device['password'] = encrypted_pass
end
self.value = devices.to_json
end
Hopefully you have some logic surrounding when this method is called as you dont want to encrypt an already encrypted password.

Why am I getting an unsupportedSchemeError

I am writing a program that uses Mechanize to scrape a student's grades and classes from edline.net using a student account and return the data I need. However, after logging in, from the homepage I have to access a link (called 'Private Reports') which will then dynamically return a page of links to each of the student's classes and respective grades.
When testing I create a new object my_account that has several instance variables including the homepage. I pointed new variables to the instance variables for this to be more simple to read):
result_page = agent.page.link_with(:text => 'Private Reports').click
I get:
Mechanize::UnsupportedSchemeError
But if I were to replace :click with :text it responds correctly and result_page will equal the link's text "Private Reports"
Why does it respond to :text correctly but give an error for :click? Is there a way to get around this or should I rethink my solution to this problem?
Here's the code:
class AccountFetcher
EDLINE_LOGIN_URL = 'http://edline.net/Index.page'
def self.fetch_form(agent)
# uses #agent of an Account object
page = agent.get(EDLINE_LOGIN_URL)
form = page.form('authenticationEntryForm')
end
# edline's login form has to have several pre-conditions met before submitting the form
def self.initialize_form(agent)
form = AccountFetcher.fetch_form(agent)
submit_event = form.field_with(:name => 'submitEvent')
enter_clicked = form.field_with(:name => 'enterClicked')
ajax_support = form.field_with(:name => 'ajaxSupported')
ajax_support.value = 'yes'
enter_clicked.value = true
submit_event.value = 1
return form
end
# logs the user in and returns the homepage
def self.fetch_homepage(u_username, u_password, agent)
form = AccountFetcher.initialize_form(agent)
username = form.field_with(:name => 'screenName')
password = form.field_with(:name => 'kclq')
username.value = u_username
password.value = u_password
form.submit
end
end
# class Account will be expanded later on but here are the bare bones for a user to log in to their account
class Account
attr_accessor :report, :agent, :username, :password
def initialize(u_username, u_password)
#agent = Mechanize.new
#username = u_username
#password = u_password
end
def login
page = AccountFetcher.fetch_homepage(self.username, self.password, self.agent)
#report = page
end
end
my_account = Account.new('ex_username', 'ex_password')
my_account.login
page = my_account.report
agent = my_account.agent
page = agent.page.link_with(:text => 'Private Reports').click
Where does the link point to? Ie, what's the href?
I ask because "scheme" usually refers to something like http or https or ftp, so maybe the link has a weird scheme that mechanize doesn't know how to handle, hence Mechanize::UnsupportedSchemeError

Ruby gmail gem and storing credentials

I'm writing a very small program that I'd like to run on my RPI in a cron job. Every hour I want to check the status of a webpage. If the status meets a certain criteria I want it to email me.
In the past I have successfully used the gmail gem however I have always had to provide my credentials. I am nervous about storing my gmail credentials on file. Does anyone know how to accomplish this task more securely?
The end goal is I want an email in my inbox that tells me that a gate status has changed on the website I'm monitoring.
Here is what I have so far
#!/usr/bin/ruby
require 'open-uri'
require 'nokogiri'
def check_gates
doc = Nokogiri::HTML(open('http://www.summitatsnoqualmie.com/Mountains/Grooming-Report'))
gates = {}
table_rows = doc.xpath('//tr')
sections = []
sections.push({:gate => "Elevator", :data => table_rows.select { |tr| tr.inspect.include? "Lower Traverse" }.first})
sections.push({:gate => "Nash", :data => table_rows.select { |tr| tr.inspect.include? "Upper Traverse" }.first})
sections.each do |section|
status_text = section[:data].element_children.select { |child| child.inspect.include? "grooming_open_status" }.first.inspect
match = status_text.match(/background-position:\ (\d+)px\ (.\d)+px/)
gate_down = false
unless match.nil?
gate_down = match[1].to_i == 0 and match[2].to_i == 0
end
gates[section[:gate]] = gate_down ? "CLOSED" : "OPEN"
end
gates
end
Generate an application-specific password for your Google account, and store that password on the server.

Ruby BCrypt hash comparison

I'm trying to implement what seems like a very simple authentication approach using Sinatra and BCrypt but clearly I'm missing something...
Users are preassigned a temporary password which is stored in plaintext in the db.
I authenticate against the temp password and then create both a salt and password_hash and write them as strings to the db (mongo in this case).
To authenticate I fetch the salt from the db and user password to compare.
post "/password_reset" do
user = User.first(:email => params[:email], :temp_password => params[:temp_password])
if dealer != nil then
password_salt = BCrypt::Engine.generate_salt
password_hash = BCrypt::Engine.hash_secret(params[:password], password_salt)
user.set(:password_hash => password_hash)
user.set(:password_salt => password_salt)
end
end
post "/auth" do
#user = User.first(:email => params[:email])
#user_hash = BCrypt::Password.new(#user.password_hash) #because the password_hash is stored in the db as a string, I cast it as a BCrypt::Password for comparison
if #user_hash == BCrypt::Engine.hash_secret(params[:password], #user.password_salt.to_s) then
auth = true
else
auth = false
end
end
The value returned by BCrypt::Engine.hash_secret(params[:password], password_salt) is different than what is stored in the db (both are of class BCrypt::Password, but they don't match).
What am I missing here? Many thanks in advance for any insight!
Marc
BCrypt::Password is a subclass of String, and it overrides the == method to make checking passwords easier. When you do
if #user_hash == BCrypt::Engine.hash_secret(params[:password], #user.password_salt.to_s)
you end up performing the hash twice, and so they don’t match. If you compared directly with #user.password_hash rather than using BCrypt::Password.new you should see that they match.
The more “correct” way to use bcrypt-ruby for passwords is to not use the Engine class at all, just the Password class. You don’t need to manage the salt yourself, bcrypt takes care of that and includes it in the password hash string:
password_salt = BCrypt::Engine.generate_salt
password_hash = BCrypt::Engine.hash_secret("s3kr1t!", password_salt)
puts password_salt
puts password_hash
produces something like this:
$2a$10$4H0VpZjyQO9SoAGdfEB5j.
$2a$10$4H0VpZjyQO9SoAGdfEB5j.oanIOc4zp3jsdTra02SkdmhAVpGK8Z6
You’ll get something slightly different if you run it, since a different salt will be generated, but you can see that the password hash includes the salt.
In your case, you want something like this:
post "/password_reset" do
user = User.first(:email => params[:email], :temp_password => params[:temp_password])
if dealer != nil then
password_hash = BCrypt::Password.create(params[:password])
user.set(:password_hash => password_hash) # no need to store the salt separately in the database
end
end
post "/auth" do
#user = User.first(:email => params[:email])
#user_hash = BCrypt::Password.new(#user.password_hash)
if #user_hash == params[:password] then # overridden == method performs hashing for us
auth = true
else
auth = false
end
end

How to check for user credentials using active directory and a ruby script

I'm trying write a Ruby script that checks if user credentials are valid using an active directory server. Here's what I've tried so far:
require 'rubygems'
require 'net-ldap'
host = '10.4.1.6'
port = 389
username = 'username'
password = 'password'
ldap = Net::LDAP.new
ldap.host = host
ldap.port = port
ldap.auth "CN=#{username},CN=Users,DC=companyname,DC=ad", password
if ldap.bind
puts 'YES!'
puts ldap.get_operation_result.message
else
puts 'NO :-('
puts ldap.get_operation_result.message
end
If I enter a non existing username and an empty string as a password, the bind operation succeeds. If I enter a valid username and a valid/invalid/empty password, the bind operation fails with error message 'Invalid Credentials'.
I've looked at other threads and read the net-ldap documentation but I can't figure out what I'm doing wrong.
Can someone give me some ideas on how to achieve this?
Thanks in advance for any replies :-)
Edit:
As #StuartEllis suggested, the problem was with the user identifier. To figure out the correct DN, I used the following script (taken from the net-ldap documentation):
ldap.auth "CN='adminUser',CN=Users,DC=companyname,DC=ad", 'adminUserPwd'
ldap.bind
treebase = "DC=companyname,DC=ad"
filter = Net::LDAP::Filter.eq( "mail", "username#companyname.com" )
attrs = ["mail", "cn", "sn","objectclass"]
ldap.search( :base => treebase, :filter => filter, :attributes => attrs, :return_result => false ) do |entry|
puts entry._dump 0
end
I then retried using my original script (above) with the obtained DN and voila!
I would guess that your LDAP account details aren't correct, but your LDAP server accepts anonymous binds, which is why it works when you don't specify a valid username and password. LDAP user identifiers are very fiddly, so I'd suggest double-checking the whole thing, including the case of the parts.
Here is sample code I use with the net-ldap gem to verify user logins from the ActiveDirectory server at my work:
def name_for_login( email, password )
email = email[/\A\w+/].downcase # Throw out the domain, if it was there
email << "#mycompany.com" # I only check people in my company
ldap = Net::LDAP.new(
host: 'ldap.mycompany.com', # Thankfully this is a standard name
auth: { method: :simple, email: email, password:password }
)
if ldap.bind
# Yay, the login credentials were valid!
# Get the user's full name and return it
ldap.search(
base: "OU=Users,OU=Accounts,DC=mycompany,DC=com",
filter: Net::LDAP::Filter.eq( "mail", email ),
attributes: %w[ displayName ],
return_result:true
).first.displayName.first
end
end

Resources