Http binding and Aruba Access Points

I’m trying to access my Aruba Access Points via http binding. My usecase is to enable/disbable guest wifi ssid.

Here is Aruba’s REST API Guide.

My first issue is that I need to send the authentication - and all other settings - in a JSON format. How can I do this? In the API Guide it is done via curl and the option --data. Is there a eqivalent using the http binding?

My second issue is the “SID”. I need to send username and password and I’m getting a “sid” (Token) which I have to use in every subsequent REST call. This sid gets unvalid after 15 min of inactivity. So I need to reauthenticate every REST-Call, I think. How could this be done?

The following is an example response for a successful login:

curl --insecure "https://IP-ADDRESS:4343/rest/login" -H "Content-Type: application/json" --data
'{"user": "admin", "passwd": "xxxxxx"}' 

Response:

{"
Status": "Success",
"sid": "m7zI7bic......."
}

Logout:

curl --insecure -k "https://IP-ADDRESS:4343/rest/logout" -H "Content-Type: application/json" --data '{"sid":"m7zI7bic......."}'

Enable SSID

curl --insecure "https://IP-ADDRESS:4343/rest/zone?sid="m7zI7bic......."" -H "Content-Type: application/json" --data '{"ssid-profile": {"action": "create", "ssid-profile": "Gast", "enable": "yes"}}'

Disable SSID

curl --insecure "https://IP-ADDRESS:4343/rest/zone?sid="m7zI7bic......."" -H "Content-Type: application/json" --data '{"ssid-profile": {"action": "create", "ssid-profile": "Gast", "disable": "yes"}}'

Yes. You have two options. You can put the full JSON into a String Item. When the Item is commanded with that JSON String it will be passed as the data in the call.

The other option is to use a transformation to convert the state of the command Item to the JSON required. I use Jinja but a Script or Map transformation might work too.

Here is the HTTP Thing I use to turn on/off AdGuard on my opnSense firewall. It uses POST instead of GET to change the status but overall it should work mostly the same with GET. I use Jinja to convert the command to JSON and the onValue and offValue fields to convert the ON/OFF to true/false…

UID: http:url:adguard
label: AdGuard
thingTypeUID: http:url
configuration:
  headers:
    - ""
  ignoreSSLErrors: true
  stateMethod: GET
  refresh: 30
  commandMethod: POST
  timeout: 3000
  authMode: BASIC_PREEMPTIVE
  baseURL: https://10.10.1.1:8443/control
  password: mypassword
  delay: 0
  contentType: application/json
  bufferSize: 2048
  username: myusername
location: Global
channels:
  - id: protection_status
    channelTypeUID: http:switch
    label: Protection Status
    description: ""
    configuration:
      mode: READWRITE
      onValue: "true"
      commandTransformation:
        - 'JINJA:{"protection_enabled": {{value}}}'
      offValue: "false"
      stateExtension: /status
      commandExtension: /dns_config
      stateTransformation:
        - JSONPATH:$.protection_enabled
  - id: last-success
    channelTypeUID: http:request-date-time
    label: Last Success
    configuration: {}
  - id: last-failure
    channelTypeUID: http:request-date-time
    label: Last Failure
    configuration: {}

DELETED - see a revised version below

Thank you rlkoshak and jimtng.

In the adguard example I’m missing the “sid” part. I Unterstand, that I have to send data via other items.

The JRuby’s version sound more promising. But how can I implement that? I’v installed the jruby extention, is that example a “execute inline script”? It’s not a .rules’ content, it couldn’t be loaded.

It’s the Jinja part. Like I said above, you need to use Jinja or a Script transformation or send the JSON to the command Item.

Once the Jinja add-on is installed, the command transformation would be something like:

JINJA:{"ssid-profile": {"action": "create", "ssid-profile": "Gast", "{{value}}": "yes"}}

And the on value property would be “enable” and the off value property would be “disable”.

How would you obtain the sid and then pass it on as a get Param at the same time, using this method?

Given the “a Thing represents a device” that would be a separate Thing. Or maybe just another Channel on this one Thing.

In other words, the SSID is hard coded in the config.

No, I was referring to sid (probably stands for Security ID), which is kind of the authorization token, i.e. you login, you get an sid, then you provide this token on each request to prove that you’re authorized to make that request.

Version 2 of the script, to make it even simpler to use from rules:

Save this as conf/automation/ruby/lib/aruba.rb

# frozen_string_literal: true

gemfile do
  source "https://rubygems.org"
  gem "faraday"
end
# Or configure the jrubyscripting addon to install these gems on startup

class Aruba
  # Static methods
  class << self
    def enable_ssid(url:, user:, password:, ssid:)
      aruba = new(url)
      aruba.login(user, password)
      aruba.enable_ssid(ssid)
      aruba.logout
      aruba.close
    end

    def disable_ssid(url:, user:, password:, ssid:)
      aruba = new(url)
      aruba.login(user, password)
      aruba.disable_ssid(ssid)
      aruba.logout
      aruba.close
    end
  end


  # @param url [String] Aruba uri prefix e.g. https://1.2.3.4:4343
  def initialize(url)
    @conn = Faraday.new(url:, ssl: { verify: false }) do |connection|
      connection.request :json
      connection.response :json
    end
    @sid = nil
  end

  def login(user, passwd)
    @conn.post("/rest/login", user:, passwd:).then do |response|
      if response.status == 200
        @sid = response.body["sid"]
      else
        @sid = nil
        raise "Login failed: #{response.body}"
      end
    end
  end

  def logout
    @conn.post("/rest/logout", { sid: @sid })
    @sid = nil
  end

  # curl --insecure "https://IP-ADDRESS:4343/rest/zone?sid="m7zI7bic......."" -H "Content-Type: application/json"
  # --data '{"ssid-profile": {"action": "create", "ssid-profile": "Gast", "enable": "yes"}}'
  def enable_ssid(ssid)
    zone({ "ssid-profile": { action: "create", "ssid-profile": ssid, enable: "yes" } })
  end

  # curl --insecure "https://IP-ADDRESS:4343/rest/zone?sid="m7zI7bic......."" -H "Content-Type: application/json"
  # --data '{"ssid-profile": {"action": "create", "ssid-profile": "Gast", "disable": "yes"}}'
  def disable_ssid(ssid)
    zone({ "ssid-profile": { action: "create", "ssid-profile": ssid, disable: "yes" } })
  end

  def zone(payload)
    @conn.post("/rest/zone", payload) do |request|
      request.params["sid"] = @sid
    end
  end

  def close
    @conn.close
  end
end

Then in your rule, add an inline script, choose Ruby, and enter this if you want to enable ssid:

require 'aruba'
Aruba.enable_ssid(url: "https://YOUR_ARUBA_IP:PORT", user: "YOURUSERNAME", password: "PASSWORD", ssid: "SSID_TO_ENABLE")

You can call Aruba.disable_ssid with the same arguments as above.

Auth stuff is handled as properties on the main HTTP Thing

My example above shows basic auth but tokens and digest et. al. are also supported.

But in the curl command above, those would just be a part of the URL. The sid part is just a url argument.

That can be set at the Thing level or at the Channel level as needed.

Hi jimtng,

I’ve used your template and created a rule using WebUI only.
Here is the whole configuration:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: jruby_aruba_wlan_gast
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/x-ruby
      script: >
        # frozen_string_literal: true


        require 'faraday'


        logger.info("Skript gestartet, Event-State: #{event.item_state}")


        # Aruba Klasse

        class Aruba
          def initialize(url)
            @conn = Faraday.new(url: url, ssl: { verify: false }) do |connection|
              connection.request :json
              connection.response :json
            end
            @sid = nil
          end

          def login(user, passwd)
            response = @conn.post("/rest/login", user: user, passwd: passwd)
            if response.status == 200
              @sid = response.body["sid"]
            else
              raise "Login fehlgeschlagen: #{response.body}"
            end
          end

          def logout
            @conn.post("/rest/logout", { sid: @sid })
            @sid = nil
          end

          def enable_ssid(ssid)
            edit_ssid({ "ssid-profile": { action: "create", "ssid-profile": ssid, enable: "yes" } })
          end

          def disable_ssid(ssid)
            edit_ssid({ "ssid-profile": { action: "create", "ssid-profile": ssid, disable: "yes" } })
          end

          def edit_ssid(payload)
            logger.info("Sende Anfrage an /rest/ssid mit Payload: #{payload.to_json}")

            response = @conn.post("/rest/ssid", payload) do |request|
              request.params["sid"] = @sid
            end

            logger.info("Antwort Status: #{response.status}, Body: #{response.body}")

            response
          end

          def close
            @conn.close
          end
        end


        # Verbindungsdaten (hier anpassen!)

        url = "https://IP-ADDRESS:4343"

        user = "admin"

        password = "PASSWORD"

        ssid = "Gast"


        begin
          aruba = Aruba.new(url)
          aruba.login(user, password)

          if event.item_state.on?
            logger.info("Gast-WLAN wird aktiviert")
            aruba.enable_ssid(ssid)
          elsif event.item_state.off?
            logger.info("Gast-WLAN wird deaktiviert")
            aruba.disable_ssid(ssid)
          else
            logger.warn("Unerwarteter Status: #{event.item_state}")
          end

          aruba.logout
        rescue => e
          logger.error("Fehler beim Steuern des Aruba WLAN: #{e.message}")
        ensure
          aruba&.close
        end
    type: script.ScriptAction

Remarks:

  • folder …/ruby/lib doesn’t exist on my installation and openhab user hasn’t write privileges to /ruby. That’s why I tried the WebUI.
  • There are some minor change in the script, e.g. the rest url is /rest/ssid not /rest/zone. It’s my mistake, I had posted the wrong url above.
  • I added faraday to the “JRuby Scripting” addon-confugartion

I’m a fan of integrated solutions, so I’ll try to implement rlkoshak’s idea the next week.

If you managed to do it with http binding, please let us know how

This can be simplified to event.on? and event.off?

I need a hint, how it could be done via HTTP Binding. I’m struggeling at the moment…

There is no need “Authentication Mode”, I think, because the credentials are send using POST and a JSON content. No username/password/token etc. is included in the URL.

Without knowing what you’ve tried :person_shrugging:

You don’t show the full URL in the original post. But you’d include the sid in the “base url” of the Thing. The Channel’s state and command URLs would include what ever is after that.

It’s the full URL. The dots .... are just hiding some characters. But the URLs is complete.
But this is the secound URL, I need an other for login.

This is my Thing for the login:

Thing http:url:aruba_login "Aruba Login" [
  baseURL="https://192.168.xx.yy:4343/rest/login",
  ignoreSSLErrors=true,
  timeout=5000,
  commandMethod="POST",
  contentType="application/json"
] {
  Channels:
    Type string : login "Login (Session-ID)" [ mode="WRITEONLY", stateTransformation="JSONPATH:$.sid", stateContent="{\"user\":\"admin\",\"passwd\":\"xxxxxxx\"}" ]
}

And this are my Items

Switch    http_aruba_wlan_gast         "Wlan Gast http"               <network>    (gAruba)
String    http_aruba_sid               "Aruba Session ID"                              (gAruba)        {channel="http:url:aruba_login:login"}

And the first part of the rule

rule "Gast WLAN Steuerung über HTTP Things mit Debug-Logs"
when
  Item http_aruba_wlan_gast received update
then
  // Create Login 
  logInfo("aruba", "Send Login")
  aruba_login_send.sendCommand(ON)
  // Short Pause
  logInfo("aruba", "Wait (2s)...")
  Thread::sleep(2000)

  logInfo("aruba", "Session-ID after Login, current Value " + http_aruba_sid.state.toString)
  val sid = http_aruba_sid.state.toString
end

How often do you need to log in? I assumed that the login was something you did once or rarely so it would be hard coded into the Thing. If you need to log in frequently that means you need to combine the state of two different Items, the sid and what ever it is that the Channel does (e.g. enable/disable) which the binding doesn’t support. You need rules for that.

Or maybe a Script profile if everything is passed as URL arguments and not as the body of the HTTP request.

I managed to create rules for this.
Using DSL rules is more advanced than jruby, because you can’t ignore certificate error in java that easy.
I had to create a proper certificate: the certificate must have at least one SAN and it must be imported into java cacerts using keytool.

After creating and installing the certificate: copy the public key into a local file

 echo -n | openssl s_client -connect 192.168.xx.yy:4343  |   sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > aruba-cert.pem 

and import it into cacerts:

sudo keytool -importcert   -file aruba-cert.pem   -alias aruba-instant   -keystore /etc/ssl/certs/java/cacerts   -storepass changeit

The location my be different, it depends your installation. This command tells you the location

readlink -f $(which java)

Afterwards I created this items, which triggers a rule:

Switch    http_aruba_wlan_gast         "Wlan Gast http"               <network>    (gAruba)

The first rule turns the ssid on or off. The second rule is for monitoring. It syncs the current status - just in case I had changed the status in Aruba WebUI, or to initialize that item after OH restart.

import java.net.URLEncoder

// Konfigurationsvariablen (global verwendbar)
// Configuration variables (used by all rules)
val baseUrl = "https://192.168.xx.yy:4343"
val username = "admin"
val password = "xxxx"
val ssidName = "Gast"
val contentType = "application/json"
val timeout = 5000
val apIp = "192.168.xx.yy" // IP-Adresse des Aruba IAP (für show-cmd)


rule "Gast WLAN Steuerung"
when
  Item http_aruba_wlan_gast received update
then
  logInfo("aruba", "WLAN-Gastschalter aktiviert – SSID soll Status: {}", http_aruba_wlan_gast.state.toString)
  
  // 2 Sekunden warten, falls rule "SSID-Status prüfen" aktiv ist; 
  Thread::sleep(2000)
  //  Anmeldung beim Aruba Controller (Login)
  val loginPayload = '{"user":"' + username + '","passwd":"' + password + '"}'
  val loginResponse = sendHttpPostRequest(baseUrl + "/rest/login", contentType, loginPayload, timeout)

  if (loginResponse === null) {
    logError("aruba", "Login fehlgeschlagen – keine Antwort")
    return
  }

  val loginStatus = transform("JSONPATH", "$.Status", loginResponse)
  if (loginStatus != "Success") {
    logError("aruba", "Login fehlgeschlagen – API-Status: {}", loginStatus)
    return
  }

  val sid = transform("JSONPATH", "$.sid", loginResponse)
  if (sid === null || sid.trim == "") {
    logError("aruba", "Session-ID fehlt trotz erfolgreichem Login")
    return
  }

  // logInfo("aruba", "Session-ID erhalten: {}", sid)
  logInfo("aruba", "Session-ID erhalten")
  // SSID aktivieren (POST zur REST API)

  // SSID-Aktion vorbereiten
  val ssidPayload = if (http_aruba_wlan_gast.state == ON)
    '{"ssid-profile":{"action":"create","ssid-profile":"' + ssidName + '","enable":"yes"}}'
  else
    '{"ssid-profile":{"action":"create","ssid-profile":"' + ssidName + '","disable":"yes"}}'

  // SSID schalten
  val ssidUrl = baseUrl + "/rest/ssid?sid=" + sid

  // logInfo("aruba", "Sende Aktivierungsbefehl für SSID '{}' an {}", ssidName, ssidUrl)
  logInfo("aruba", "Sende Aktivierungsbefehl für SSID '{}'", ssidName)
  val ssidResponse = sendHttpPostRequest(ssidUrl, contentType, ssidPayload, timeout)

  if (ssidResponse === null) {
    logError("aruba", "SSID-Steuerung fehlgeschlagen")
  } else {
    // logInfo("aruba", "SSID erfolgreich aktiviert: {}", ssidResponse)
    logInfo("aruba", "SSID erfolgreich neuen Status gesetzt: {}", http_aruba_wlan_gast.state.toString)
  }

  //  Abmelden (Logout)
  val logoutPayload = '{"sid":"' + sid + '"}'
  val logoutUrl = baseUrl + "/rest/logout"
  val logoutResponse = sendHttpPostRequest(logoutUrl, contentType, logoutPayload, timeout)

  if (logoutResponse === null) {
    logWarn("aruba", "Logout fehlgeschlagen")
  } else {
    logInfo("aruba", "Logout erfolgreich")
  }
end





rule "SSID-Status prüfen bei Systemstart"
when
  Item A_SystemStartet received update OFF or 
  Item http_aruba_wlan_gast_status received update or
  Time cron "0 24 * * * ?" // jede Stunde *:24
then
  logInfo("aruba", "Starte SSID-Statusprüfung")

  // Login
  val loginPayload = '{"user":"' + username + '","passwd":"' + password + '"}'
  val loginResponse = sendHttpPostRequest(baseUrl + "/rest/login", contentType, loginPayload, timeout)

  if (loginResponse === null) {
    logError("aruba", "Login fehlgeschlagen – keine Antwort")
    return
  }

  val loginStatus = transform("JSONPATH", "$.Status", loginResponse)
  if (loginStatus != "Success") {
    logError("aruba", "Login fehlgeschlagen – API-Status: {}", loginStatus)
    return
  }

  val sid = transform("JSONPATH", "$.sid", loginResponse)
  if (sid === null || sid.trim == "") {
    logError("aruba", "Session-ID konnte nicht extrahiert werden")
    return
  }

  // logInfo("aruba", "Session-ID erhalten: {}", sid)
  logInfo("aruba", "Session-ID erhalten")

  // CLI-Command abrufen: show running-config
  val cmd = "show running-config"
  val encodedCmd = URLEncoder::encode(cmd, "UTF-8")
  val showCmdUrl = baseUrl + "/rest/show-cmd?sid=" + sid + "&iap_ip_addr=" + apIp + "&cmd=" + encodedCmd

  val cliResponse = sendHttpGetRequest(showCmdUrl, timeout)
  if (cliResponse === null) {
    logError("aruba", "Keine Antwort auf show-cmd erhalten")
  } else {
    val outputRaw = transform("JSONPATH", "$.['Command output']", cliResponse)
    val output = outputRaw.replace("\\n", "\n")

    //logInfo("aruba", "CLI-Ausgabe:\n{}", output)

    // SSID-Zustand ermitteln (enabled/disabled)
    if (output.contains("wlan ssid-profile " + ssidName + "\n enable")) {
      logInfo("aruba", "SSID '{}' ist AKTIV", ssidName)
      http_aruba_wlan_gast.postUpdate(ON)
    } else if (output.contains("wlan ssid-profile " + ssidName + "\n disable")) {
      logInfo("aruba", "SSID '{}' ist DEAKTIVIERT", ssidName)
      http_aruba_wlan_gast.postUpdate(OFF)
    } else {
      logWarn("aruba", "SSID '{}' nicht eindeutig im CLI-Output erkannt", ssidName)
    }
  }

  // Logout
  val logoutPayload = '{"sid":"' + sid + '"}'
  val logoutResponse = sendHttpPostRequest(baseUrl + "/rest/logout", contentType, logoutPayload, timeout)

  if (logoutResponse === null) {
    logWarn("aruba", "Logout fehlgeschlagen")
  } else {
    logInfo("aruba", "Logout erfolgreich")
  }
end

thank you for your helb, rlkoshak and @jimtng !

In jruby if you want Faraday to fail when the certificate is unknown, remove the verify: false option. I added it there to match your curl example.

You can also import the certs to verify it.

Woah these Aruba access points can be had for pretty cheap on the US second hand market. The 515 and 535 in particular, look extremely interesting spec wise…

1 Like