PHP – LDAP Authentication and Getting LDAP User Groups for user

Some pretty useful functions to check if LDAP Username/Password entered are valid. And to get LDAP Groups that a user belongs to so group based access can be added.

$host = '';
$port = '636';
$protocol = 'ldaps';
$base_dn = 'ou=corp,dc=domain,dc=pvt';
$domain = "@domain.pvt";
function validAdminUser($uname) {
    // Returns true if user belongs in group support/it/systems
    $validAdminUser = false;
    $arr_usr_groups = get_groups($uname);
    foreach($arr_usr_groups as $group) {
        if(strpos(strtolower($group), "cn=support,") !== false ||
           strpos(strtolower($group), "cn=it,") !== false ||
           strpos(strtolower($group), "cn=systems,") !== false) {
            $validAdminUser = true;
    return $validAdminUser;

function ldap_login($username, $password) {
    global $host, $port, $protocol, $base_dn, $domain;
    if ($username && $password) {
        $connection_string = "$protocol://$host:$port";
        $conn = @ldap_connect($connection_string) or $msg = "Could not connect: $connection_string";
        ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($conn, LDAP_OPT_REFERRALS, 0);
        $ldaprdn = $username.$domain;
        $ldapbind = @ldap_bind($conn, $ldaprdn, $password);
        if ($ldapbind) {
            $search = ldap_search($conn, $base_dn, "(samaccountname=$username)");
            if ($search) {
                $result = ldap_get_entries($conn, $search);
                if ($result['count'] > 0) {
                    $returnval = 1; // "Success"
                else {
                    $returnval = -1; // "User not found"
        else {
            $returnval = 0; // "Incorrect username/password"
    else {
        $returnval = -1; // "Please enter username/password"
    return $returnval;    

function get_groups($user) {
    global $host, $port, $protocol, $base_dn, $domain;

    // Use admin user in LDAP to query
    $username = "admin_username";
    $password = "password";
	// Active Directory server
	$connection_string = "$protocol://$host:$port";
	// Active Directory DN, base path for our querying user
	$ldap_dn = $base_dn;
	// Active Directory user for querying
	$query_user = $username."$domain";
	$password = $password;
	// Connect to AD
	$ldap = ldap_connect($connection_string) or die("Could not connect to LDAP");
	ldap_bind($ldap,$query_user,$password) or die("Could not bind to LDAP");
	// Search AD
	$results = ldap_search($ldap,$ldap_dn,"(samaccountname=$user)",array("memberof","primarygroupid"));
	$entries = ldap_get_entries($ldap, $results);
	// No information found, bad user
	if($entries['count'] == 0) return false;
	// Get groups and primary group token
	$output = $entries[0]['memberof'];
	$token = $entries[0]['primarygroupid'][0];
	// Remove extraneous first entry i.e. the count of the groups the user belongs to
	// We need to look up the primary group, get list of all groups
	$results2 = ldap_search($ldap,$ldap_dn,"(objectcategory=group)",array("distinguishedname","primarygrouptoken"));
	$entries2 = ldap_get_entries($ldap, $results2);
	// Remove extraneous first entry
	// Loop through and find group with a matching primary group token
	foreach($entries2 as $e) {
		if($e['primarygrouptoken'][0] == $token) {
			// Primary group found, add it to output array
			$output[] = $e['distinguishedname'][0];
			// Break loop
	return $output;

Adding my first validation rule in Salesforce

It’s exciting to do Salesforce development and administration. Had to add a validation or security for a field so it’s only editable by a certain set of people or user profiles. I added a Validation Rule under Customize > Leads > Validation Rules > New.

Please note that the validation rules work on the entire object by default unless specific fields are defined within the ruleset. The following validation would enable the modification by the given set of users AND the System Administrator AND only if the Sales Channel field has been changed. The use case is to make the Sales Channel field editable only to the given set of users. So the Validation Rule will only come into picture if Sales Channel has been modified (ISCHANGED) and in which case if the user isn’t in the list, saving the object would result in a Warning/Error. If you accidentally omit the ISCHANGED part, it would imply that the Lead object itself is only modifiable by the given set of users or user groups.

NOT ($User.Id == "0053300000467Gs" || $User.Id == "005f3000004pNZ7" || $User.Id == "00540000001Ej68" ||
$Profile.Name == "System Administrator" || $Profile.Name == "Accellion Order Management") && ISCHANGED(Sales_Channel__c)

A slight tweak to the above use case. Make the field editable to only a few users or groups AND only if the prior value in the field is “From Partner”.

ISPICKVAL(PRIORVALUE(Sales_Channel__c), "From Partner") && NOT ($User.Id == "0053300000467Gs" || $User.Id == "005f3000004pNZ7" || $User.Id == "00540000001Ej68" ||
$Profile.Name == "System Administrator" || $Profile.Name == "Accellion Order Management") && ISCHANGED(Sales_Channel__c)

Personally, I prefer to use the logical && and || symbols rather than the AND/OR functions in Salesforce because it’s easily readable. Here’s a validation rule that will raise an error if:
1. Opportunity Amount when selecting Customer Type = ‘New Customers’ with Amount less than $5000 not allowed; OR
2. Opportunity Amount when selecting Customer Type = ‘Existing Customer – Replacement’ with Amount less $4000 not allowed;

(Amount < 5000 && ISPICKVAL(Type, 'New Customer')) || (Amount < 4000 && ISPICKVAL(Type, 'Existing Customer - Replacement'))

Salesforce – Implementing Triggers and Callouts

Here’s a simple use case that I implemented within Salesforce.

On saving a contact:
1. Set a field to be always true for the Contact
2. Call an external API

Create a new Trigger
To begin with, Setup > Customize > Contacts > Triggers. Create a New trigger and name it say AddPartnerToKiteworks. I’ve chosen after insert and after update as trigger points because I want the target functionality for newly created contacts as well as contacts that are modified.

trigger AddPartnerToKiteworks on Contact (before insert, before update) {
    System.debug(LoggingLevel.Info,'Executing trigger');
    for(Contact c : {
        // Set the value of DoNotCall (just as an example) to always be true
        c.DoNotCall = true;
        // KiteworksConnector is a Callout class that is defined in the next section with a static method makeCallout();

Create the callout class
Navigate to Setup > Build > Develop > Apex Classes. Create a New trigger and name it to be same as the class definition so it’s easy to remember. On calling the trigger, I am executing a hard-coded endpoint just to test this out but you are free to call your own API endpoint or web service.

public class KiteworksConnector {
    public static void makeCallout() {
        HttpRequest request = new HttpRequest();
        // Set the endpoint URL.
        String endpoint = '';
        // Set the HTTP verb to GET.
        // Send the HTTP request and get the response.
        HttpResponse response = new HTTP().send(request);
        if (response.getStatusCode() == 200) {
            System.debug(LoggingLevel.Info,'Successful call');

Add a secure endpoint
Navigate to Setup > Administer > Security Controls > Remote Site Settings. Create a New Remote Site and name it to say jsonplaceholder_test_api and add the URL for the site. In this case it is Check the Active flag and Save the site information.

Checking debug logs
Check out the following blog post to understand how to enable debug logs.

Some useful links:

  • APEX Triggers Introduction
  • APEX Integration REST Callouts
  • Enabling and Forcing HTTPS on nginx server

    Getting used to nginx. Just configured nginx to enable HTTPS and also force HTTPS by redirecting HTTP to HTTPS.

    1. Get the SSL certs on the server – Navigate to /etc/ssl. Create your own directory with the domain name if you wish to or you could please your SSL Certs with the existing certs and private sub-directories under there. I preferred to create a new directory so it’s easy to manage the certs created by us. Place the following 2 files in the directory, e.g.


    2. Modify the nginx configuration – Navigate to /etc/nginx/sites-available. Open the file redash that is created during the server setup and add the following lines of code so the final file looks something like this (The pieces of code in Bold are the ones that were added).

    upstream rd_servers {
    server {
      server_tokens off;
      listen 80;
      return 301 https://$server_name$request_uri;
      access_log /var/log/nginx/rd.access.log;
      gzip on;
      gzip_types *;
      gzip_proxied any;
      location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass       http://rd_servers;
    server {
      server_tokens off;
      listen 443;
      ssl on;
      ssl_certificate /etc/ssl/;
      ssl_certificate_key /etc/ssl/;
      ssl_protocols TLSv1.2;
      access_log /var/log/nginx/rd.access.log;
      gzip on;
      gzip_types *;
      gzip_proxied any;
      location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass       http://rd_servers;

    The following lines of code will redirect the HTTP traffic to HTTPS. Only required if you completely want to disable HTTP.

    return 301 https://$server_name$request_uri;

    Install Redash server On-Prem on Debian

    I followed setup instructions to install the redash server on-prem. There’s a provisioning script to install the redash server on-prem. But as expected, things don’t really work first time on all environments. I used the latest Debian (stretch) v9.2.1 and ran into issues running the provisioning script. Then I had to manually debug and go through each step/package installation at a time and found the following 2 issues.

    1. pip installation cryptography didn’t work. I eliminated this step. Remove the following line from the file /opt/redash/current/requirements.txt


    2. The pyOpenSSL package wasn’t compatible so I had to replace the version in requirements_all_ds.txt file under /opt/redash/current/ directory


    Original Script for reference

    # This script setups Redash along with supervisor, nginx, PostgreSQL and Redis. It was written to be used on 
    # Ubuntu 16.04. Technically it can work with other Ubuntu versions, but you might get non compatible versions
    # of PostgreSQL, Redis and maybe some other dependencies.
    # This script is not idempotent and if it stops in the middle, you can't just run it again. You should either 
    # understand what parts of it to exclude or just start over on a new VM (assuming you're using a VM).
    set -eu
    REDASH_BRANCH="${REDASH_BRANCH:-master}" # Default branch/version to master if not specified in REDASH_BRANCH env var
    REDASH_VERSION=${REDASH_VERSION-2.0.1.b3080} # Install latest version if not specified in REDASH_VERSION env var
    cd /tmp/
    verify_root() {
        # Verify running as root:
        if [ "$(id -u)" != "0" ]; then
            if [ $# -ne 0 ]; then
                echo "Failed running with sudo. Exiting." 1>&2
                exit 1
            echo "This script must be run as root. Trying to run with sudo."
            sudo bash "$0" --with-sudo
            exit 0
    create_redash_user() {
        adduser --system --no-create-home --disabled-login --gecos "" redash
    install_system_packages() {
        apt-get -y update
        # Base packages
        apt install -y python-pip python-dev nginx curl build-essential pwgen
        # Data sources dependencies:
        apt install -y libffi-dev libssl-dev libmysqlclient-dev libpq-dev freetds-dev libsasl2-dev
        # SAML dependency
        apt install -y xmlsec1
        # Storage servers
        apt install -y postgresql redis-server
        apt install -y supervisor
    create_directories() {
        mkdir -p $REDASH_BASE_PATH
        chown redash $REDASH_BASE_PATH
        # Default config file
        if [ ! -f "$REDASH_BASE_PATH/.env" ]; then
            sudo -u redash wget "$FILES_BASE_URL/env" -O $REDASH_BASE_PATH/.env
        COOKIE_SECRET=$(pwgen -1s 32)
    extract_redash_sources() {
        sudo -u redash wget "$LATEST_URL" -O "$REDASH_TARBALL"
        sudo -u redash mkdir "$VERSION_DIR"
        sudo -u redash tar -C "$VERSION_DIR" -xvf "$REDASH_TARBALL"
        ln -nfs "$VERSION_DIR" $REDASH_BASE_PATH/current
        ln -nfs $REDASH_BASE_PATH/.env $REDASH_BASE_PATH/current/.env
    install_python_packages() {
        pip install --upgrade pip
        # TODO: venv?
        pip install setproctitle # setproctitle is used by Celery for "pretty" process titles
        pip install -r $REDASH_BASE_PATH/current/requirements.txt
        pip install -r $REDASH_BASE_PATH/current/requirements_all_ds.txt
    create_database() {
        # Create user and database
        sudo -u postgres createuser redash --no-superuser --no-createdb --no-createrole
        sudo -u postgres createdb redash --owner=redash
        cd $REDASH_BASE_PATH/current
        sudo -u redash bin/run ./ database create_tables
    setup_supervisor() {
        wget -O /etc/supervisor/conf.d/redash.conf "$FILES_BASE_URL/supervisord.conf"
        service supervisor restart
    setup_nginx() {
        rm /etc/nginx/sites-enabled/default
        wget -O /etc/nginx/sites-available/redash "$FILES_BASE_URL/nginx_redash_site"
        ln -nfs /etc/nginx/sites-available/redash /etc/nginx/sites-enabled/redash
        service nginx restart

    3. Setup mail server
    When you open redash, you’ll get a message notifying that the mail server has not been setup. The setup page doesn’t have much details about how to setup mail configuration and troubleshoot. After some trial and error, I was able to configure the mail server properly.

    Test sending email from console. This would throw an error if the configuration/setup is incomplete and notify what’s missing.

    cd /opt/redash/current
    bin/run ./ send_test_mail

    The Setup Page has details about some environment variables that need to be setup for Mail Configuration. But just exporting them on the console didn’t work. There are a couple of places that you can enter these settings – a .env file that stores the environment variables (PREFERRED) and a settings file that reads from the env file. Prefer storing the configuration in the env file so settings stay pristine and is not hard-coded. Here is a detailed list of Environment Variables with descriptions that redash supports and can be configured.

    # Setting redash environment variables (PREFERRED)
    # Settings file that reads from the Environment Variables.

    After setting the Environment Variables, do have to restart all processes (not just nginx)

    # Restart all processes
    sudo supervisorctl restart all
    # Restart the Web server
    sudo supervisorctl restart redash_server
    # Restart Celery workers
    sudo supervisorctl restart redash_celery

    This maintenance page has information about ongoing maintenance and troubleshooting steps for the redash server.

    Convert Array to CSV string for SQL operations

    The PHP function implode() can be used to convert an Array to a comma separated string. But if you’d like the string to be a part of an SQL WHERE IN statement, they would need to be enclosed within single quotes. Here’s what you can use to accomplish a comma separated string enclosed within single quotes.

    $statusToProcess = array('Pending','Approved');
    print implode(",", $statusToProcess); \\ Outputs: Pending,Approved
    print "\n";
    print "'".implode("','", $statusToProcess)."'"; \\ Outputs: 'Pending','Approved'
    print "\n";