I connect to an LDAP server as Bob:
Hashtable props = new Hashtable();
props.put(Context.SECURITY_PRINCIPAL, "cn=Bob,cn=Users,dc=myCompany,dc=com");
props.put(Context.SECURITY_CREDENTIALS, "Password1");
props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
props.put(Context.PROVIDER_URL, url);
InitialLdapContext context = new InitialLdapContext(props, null);
Now I want to see Bob's name and email address.
I was expecting to find a method on context to return the attributes of the currently connected user, but I can't find one.
Bob doesn't have permission to search the directory, so I can't use context.search after connecting.
Is there a way to get these attributes without making another call to the LDAP server, or having to connect as someone else first?
You need to use the RFC 4532 'whoami' extended operation, OID=1.3.6.1.4.1.4203.1.11.3. You'll need to write yourself an ExtendedRequest and ExtendedResponse class for that (sorry can't post mine, but it's simple), and use it as follows:
WhoAmIExtendedResponse response = (WhoAmIExtendedResponse)context.extendedOperation(new WhoAmIExtendedRequest());
and then get the authZId out of the response. That's the DN of the current user, prefixed by "dn:". You then get his attributes via context.getAttributes("...")
Related
i'm developping a web application in java. During develop of email sender i have to specify credentials (email and password) of my host.
i was doing something like this:
public void sendEmail(String toAddress,
String subject, String message) throws AddressException,
MessagingException {
// sets SMTP server properties
Properties properties = new Properties();
properties.put("mail.smtp.host", smtp.gmail.com);
properties.put("mail.smtp.port", 587);
properties.put("mail.smtp.auth", "true");
properties.put("mail.smtp.starttls.enable", "true");
// Sender's email ID needs to be mentioned
final String username = "user_mail";
final String password = "password_mail";
// creates a new session with an authenticator
Authenticator auth = new Authenticator() {
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
};
Session session = Session.getInstance(properties, auth);
// creates a new e-mail message
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(username));
InternetAddress[] toAddresses = { new InternetAddress(toAddress) };
msg.setRecipients(Message.RecipientType.TO, toAddresses);
msg.setSubject(subject);
msg.setSentDate(new Date());
// set plain text message
msg.setText(message);
// sends the e-mail
Transport.send(msg);
}
i think that the better practice to use credentials is to save them in an xml file, and calling them back using some method, maybe Context's method.
Can someone explain me (or give me some link where i can see) how can i do that or some better?
any help will be appreciated.
One interesting way to do it is to use Vault. It's a system for managing "secrets" like credentials, API keys, etc.
There are other things you can do like write some code to automatically encrypt and decrypt the password.
As a rule of thumb, it's also important to make sure that you don't install the JDK on a production machine. If you do, a hacker would get access to javap which is the Java disassembler. The output wouldn't be that good, but probably enough to get a ballpark idea of the method you are using to secure the password.
It depends how much effort you want to place in the hiding the secrets. At the end - you have to store the credentials somewhere. You can encrypt them, but you have to store the encryption key somewhere anyway.
Many (even mature) systems simply store the username / password as plains in a configuration file and rely on the hosting to make the system secure. Some systems encrypt the passwords, but the encryption key is available anyway somewhere in a confguration and keystore. It makes retrieving your email password a little bit more complex, but not much.
The most secure way I've seen was creating a key based on a configured secret (password) and some environment property (IP, MAC, ...).
So - lets be practical. Storing your SYSTEM credentials as plains in most of the cases may be ok. You have to secure access to your system so there should be no way to get the configuration file.
I am using the scopes: mail.send, mail.readwrite, mail.read, offline_access, openid, email, and profile (although I am fairly confident I do not need all of these -> goal is to read inbox and send emails, while also getting email and name if they exist).
I am then connecting to SMTP server with the following code:
OAuth2Authenticator.connectToSmtp("smtp-mail.outlook.com",
587,
user.getOutlookUid(),
accessToken,
true);
The code that actually connects to the server is here:
public static SMTPTransport connectToSmtp(String host, int port, String userEmail, String oauthToken, boolean debug)
throws Exception {
Properties props = new Properties();
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.starttls.required", "true");
props.put("mail.smtp.sasl.enable", "true");
props.put("mail.smtp.sasl.mechanisms", "XOAUTH2");
props.put("mail.smtp.sasl.mechanisms.oauth2.oauthToken", oauthToken);
Session session = Session.getInstance(props);
session.setDebug(debug);
URLName unusedUrlName = null;
SMTPTransport transport = new SMTPTransport(session, unusedUrlName);
// If the password is non-null, SMTP tries to do AUTH LOGIN.
String password = "";
transport.connect(host, port, userEmail, password);
return transport;
}
Okay, now I can get to the most frustrating part... I have used the "connectToSMTP" method to connect to Gmail and it worked perfectly.
OAuth2Authenticator.connectToSmtp("smtp.gmail.com",
587,
user.getGoogleUid(),
accessToken,
true);
So ultimately my question is "what am I doing wrong?" or "what can I update to be able to send emails through Outlook"? I have seen that Outlook has a REST API, but that is plan B. Is there something different about Outlook vs Gmail?
Some things I have considered:
Scope did not request enough access (so I probably am asking for too much now)
access_token was stored incorrectly or encoded in some way (tried decoding it from base_64 which provided nothing). I am able to use my refresh_token to update the access_token so that tells me I am probably storing them correctly.
I tried passing null for the password. Also passed in the actual password and that WORKED, but I have the access_token and refresh_token so I shouldn't need to ask for their explicit password. Also this would be dangerous and sketchy to ask of users.
I tried manually connecting to the smtp server using "openssl s_client -crlf -starttls smtp -connect smtp-mail.outlook.com:587", but it seemed to think my access_token was wrong "535 5.0.0 OAuth failed: OAuth authentication failed due to Invalid token. Code -2147184118" That number when taken two's complement and converted to hex is 0x8004920a. Helped in searches but was to no avail.
I have done a lot of searching for this and will continue now to post this everywhere. A lot of resources for it working with Gmail, but as previously stated I already have it working for Gmail. Something seems different for Outlook. Also I have encountered lots of posts regarding email forwarding on an email client... I am semi-creating an email client so going through outlook.com settings doesn't help me.
Another concern that a buddy of mine had was that my access token was really long, contributing to what the manual smtp server claimed. It is 1188 characters long. It's something like 'EwB4Aul3BAAUo4xeBIbHjhBxWOFekj4Xy2...x9stHxi2K/VFggE=' (obviously I hid most of the characters).
Preemptive THANK YOU for anyone who offers advice or finds my issue. Especially why I can pass in the email password and that fails, but using the oauth access_token fails.
Try using "pop3://user:password#host:port/INBOX". to retrieve email from the inbox ,
more information can be found out https://javamail.java.net/docs/api/com/sun/mail/pop3/package-summary.html
hope this helps : https://technet.microsoft.com/en-ca/dn44016. Properties props = new Properties();
props.put("mail.imap.ssl.enable", "true"); // required for Gmail
props.put("mail.imap.auth.mechanisms", "XOAUTH2");
Session session = Session.getInstance(props);
Store store = session.getStore("imap");
store.connect("imap.gmail.com", username, oauth2_access_token);
This is my first post here. I have tried to search in internet for my question but found no answers. I hope your expertise will help me.
I noticed something annoying when working with Microsoft LDAP in particular. In LDAP when the user must reset their password at next logon there is a flag "User must reset password at next logon", lets call it flag X. When X is set for a user and if we try to change this user's pass we cannot. I want to do this from the name of the user not as another administrative user - so it becomes that user must change pass but if you try to change it you cannot..
Here is what I am doing:
Hashtable env = new Hashtable();
env.put(Context.SECURITY_AUTHENTICATION, <auth_mech>);
env.put(Context.SECURITY_PRINCIPAL, <principal>);
env.put(Context.SECURITY_CREDENTIALS, <pass>);
DirContext ctx = new InitialDirContext(env);
When trying to create the new Initial Context:
Authentication exception [LDAP: error code 49 - 80090308: LdapErr: DSID-0Cxxxxxx, comment: AcceptSecurityContext error, data 773, vece]
data 773 means - user must change their pass at next logon. So when:
- user must change their pass at next logon
- user pass is expired
- user account is disabled
- user account is expired
and so on we cannot change the user' password... This seems to me as a big limitation.
Also I want to ask if someone knows a specification that Micrisoft are following regarding this error codes. I know that error code 49 is general error code and is according to specification, but this data 773, data 532 are they general for all LDAP as I think they are specific to the vendor which is again very annoying.
P.S. I found similar issue here:
Change AD user expired password in Java
but it was resolved when using AD Super user that can change everyone's password, but I don't want that, I need the user to be able to change their own passwords as this is reflects in the LDAP Password History.
Thanks
The user can't change his own password if he can't login. If his password is expired you have to reset it as an administrator and then log him in with that password and force him to change it immediately.
#Valentin Mezev:
Use account Admin getLdapContext, use this context to change password of another account.
Example:
LdapContext context = getLdapContext(url, dn_admin, pass_admin);
context.modifyAttributes(dn_change, modificationItems);
I am trying to authenticate a user through LDAP against Active Directory. Following is the code snippet I use:
private DirContext bindAsUser(String bindPrincipal, String password) {
Hashtable<String,String> env = new Hashtable<String,String>();
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
env.put(Context.PROVIDER_URL, bindUrl);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.REFERRAL, "follow");
try {
return new InitialLdapContext(env, null);
} catch (NamingException e) {
e.printStackTrace()
}
}
The code for binding works if I provide:
Down-Level Logon Name, i.e. NetBIOSDomainName\sAMAccountName (e.g. domain\username), or
userPrincipalName (e.g. username#abc.com), or
distinguishedName (e.g. CN=username,OU=xxx,DC=abc,DC=com), or
objectSid (e.g. S-1-5-21-3623811015-3361044348-30300820-1013)
as the SECURITY_PRINCIPAL, while it failed if sAMAccountName (e.g. username) was used (I guess only the names which are unique within the forest are valid).
So what are the accepted patterns for SECURITY_PRINCIPAL? I searched a few similar questions, but none provide reference to official AD/LDAP documents. Or is it a configuration which I could lookup somewhere? Thanks!
From [MS-ADTS: Active Directory Technical Specification], the official doc for AD I guess.
http://msdn.microsoft.com/en-us/library/cc223499.aspx
Section "5.1.1.1.1 Simple Authentication" lists all the name forms supported by simple authentication.
I think you need check LDAP Principal Template. It specifies the principal authentication template required by your LDAP server. The principal authentication template is the format in which the authentication information for the security principal (the person who is logging in) must be passed to the LDAP server. The default value is ${email}, which is the format required by Microsoft Active Directory. Other LDAP servers require different authentication templates. Check with your network administrator to learn more about your LDAP server.
I want to search a user from LDAP and after getting the user I want to connect (validate)
that particular user using his DN and Password
I have successfully getting the DN but dont know how to bind it?
Here is an example that I took from the official documentation :
// Set up the environment for creating the initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
DirContext ctx = new InitialDirContext(env);
You have to choose your right authentication model. I have tried it before and worked fine.
The LDAP bind() operation corresponds to the following in JNDI:
Constructing an InitialDirContext or InitialLdapContext with enough information in the environment to cause a login, i.e. a security principal and credentials, or
Calling reconnect() on an LdapContext initially obtained without any security information in the environment, or with security information relating to a different principal, but whose environment has subsequently been modified.
When a connection is made to a directory server using LDAP, the connection state is unauthenticated. Requests can be transmitted on an unauthenticated connection, assuming the server administrators permit unauthenticated requests. The BIND request is used to change authentication state of a connection.
Here is an example of searching and authenticating using the UnboundID LDAP SDK: SimpleBindExample.java. This example searches for an entry given a base object, naming attribute, and username, and then attempts to authenticate using a simple bind. Examples using a SASL bind could be constructed just as easily.
If you already have LdapContext opened using your credentials, you can copy it, change principal+credential in its environment and try reconnect:
LdapContext userContext = ldapContext.newInstance(null); // copy context
userContext.addToEnvironment(InitialDirContext.SECURITY_PRINCIPAL, userDn);
userContext.addToEnvironment(InitialDirContext.SECURITY_CREDENTIALS, password);
userContext.reconnect(null); // throws NamingException if creds wrong
userContext.close();
If it throws NamingException, credentials are wrong. It it is successful, credentials are ok. ;)
(This is useful if you have only LdapContext, but not the InitialDirContext, available.)