Posts Tagged ‘python’

VPN Gnome Panel Applet
Thursday, May 15th, 2008

As part of the effort to support linux users connecting to our IPSEC/L2TP based VPN, I developed my first ever gnome panel applet written in Python (another first for me). I have a functional bash script that can be used to connect to/disconnect from the VPN, but I wanted an easier way for users to interact with the script. As a result, I developed the applet to display connection status, prompt for connection credentials and to manage the connection process so users don’t have to open a terminal and work at the command line.

The vpn shell script indicates status through a /var/lock/subsys/VPN file. The applet below constantly polls status by checking for the existence of this file (see the status_timeout method). Once the user has clicked on the applet icon, and entered the appropriate credentials, if necessary, the vpn shell script is invoked accordingly using the Popen object from the subprocess module.

This is (roughly) the python script implementing the vpn gnome panel applet:


#!/usr/bin/env python
import pygtk
pygtk.require('2.0')

import gtk
import gnomeapplet
import sys
import gobject
import subprocess
import os
import time
import logging

class VPN_Applet(gnomeapplet.Applet):

    applet = None
    image_file = "/usr/local/share/vpn/applet/images/vpn.png"
    image_file_hover = "/usr/local/share/vpn/applet/images/vpn_hover.png"
    proc_file = "/var/lock/subsys/VPN"
    username = ""
    passwd = ""
    title = "VPN Applet"
    cancel = False
    logging.basicConfig(level=logging.DEBUG)

    def __init__(self, applet, iid):
        logging.debug("__init__")

        self.applet = applet
        size = self.applet.get_size() - 2
        pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.image_file, size, size)
        image = gtk.Image()
        image.set_from_pixbuf(pixbuf)
        tooltips = gtk.Tooltips()
        tooltips.set_tip(image, "Click to connect to VPN\nStatus: Disconnected")
        # Status update timeout (check every 3 seconds)
        gobject.timeout_add(3000, self.status_timeout, image, tooltips)
        self.applet.add(image)
        self.applet.connect("button-press-event", self.button_press)
        self.applet.connect("change-size", self.change_size, image)
        self.applet.connect("change-background", self.change_background)
        self.applet.connect("enter-notify-event", self.enter_notify, image)
        self.applet.connect("leave-notify-event", self.leave_notify, image)
        self.applet.show_all()

    def check_connected(self):
        if (os.path.exists(self.proc_file)):
            return True
        else:
            return False

    def status_timeout(self, image, tooltips):
        logging.debug("status_timeout")
        if (self.check_connected()):
            txt = "disconnect from"
            status = "Connected"
        else:
            txt = "connect to"
            status = "Disconnected"
        tooltips.set_tip(image, "Click to " + txt + " VPN\nStatus: " + status)
        return True

    def do_image(self, image_file, image):
        logging.debug("do_image")

        size = self.applet.get_size() - 2
        pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(image_file, size, size)
        image.set_from_pixbuf(pixbuf)        

    def change_size(self, applet, new_size, image):
        logging.debug("change_size")

        self.do_image(self.image_file, image)

    def change_background(self, applet, type, color, pixmap):
        logging.debug("change_background")

        applet.set_style(None)
        applet.modify_style(gtk.RcStyle())
        if (type == gnomeapplet.COLOR_BACKGROUND):
            applet.modify_bg(gtk.STATE_NORMAL, color)
        elif (type == gnomeapplet.PIXMAP_BACKGROUND):
            applet.get_style().bg_pixmap[gtk.STATE_NORMAL] = pixmap

    def enter_notify(self, applet, event, image):
        logging.debug("enter_notify")

        self.do_image(self.image_file_hover, image)

    def leave_notify(self, applet, event, image):
        logging.debug("leave_notify")

        self.do_image(self.image_file, image)

    def button_press(self, button, event):
        logging.debug("button_press")

        if (event.button == 1):
            logging.debug("create dialog")
            self.setup_dialog()
            if (not(self.cancel)):
                if (not(self.check_connected())):
                    # Prompt for username/password
                    # Continue prompting until they are non-empty or user cancels
                    while ((self.username == "" or self.passwd == "") and not(self.cancel)):
                        self.username_prompt()
                    # If the user doesn't want to cancel, complete the connection
                    if (not(self.cancel)):
                        logging.debug("gksudo vpn start")
                        args = "-n '" + self.username + "' -p '" + self.passwd + "'"
                        proc = subprocess.Popen("gksudo -D '" + self.title + "' \"vpn " + args + " start\"",
                                                stdout=subprocess.PIPE,
                                                stderr=subprocess.STDOUT,
                                                shell=True)
                        self.show_progress_bar(proc)
                    # Clear username/password here
                    self.username=""
                    self.passwd=""
                else:
                    logging.debug("gksudo vpn stop")
                    proc = subprocess.Popen("gksudo -D \"" + self.title + "\" \"vpn stop\"",
                                            stdout=subprocess.PIPE,
                                            stderr=subprocess.STDOUT,
                                            shell=True)
                    self.show_progress_bar(proc)

    def setup_dialog(self):
        logging.debug("setup_dialog")

        vpn_dialog = gtk.Dialog(title=self.title,
                                flags=(gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR),
                                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
                                         gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
        # Test here for vpn connection status and prompt accordingly
        if (self.check_connected()):
            txt = "disconnect from"
        else:
            txt = "connect to"
        vpn_dialog.connect("response", self.dialog_response)
        dialog_label = gtk.Label("Would you like to " + txt + " the Virtual Private Network?")
        dialog_label.set_line_wrap(True)
        vpn_dialog.vbox.pack_start(dialog_label, padding=10)
        vpn_dialog.show_all()
        vpn_dialog.run()

    def dialog_response(self, dialog, response_id):
        logging.debug("dialog_response")

        dialog.destroy()
        if (response_id == gtk.RESPONSE_ACCEPT):
            self.cancel = False
        else:
            self.cancel = True

    def show_progress_bar(self, proc):
        logging.debug("show_progress_bar")

        if (self.check_connected()):
            txt = "Disconnecting from "
        else:
            txt = "Connecting to "
        progress_dialog = gtk.Dialog(title=self.title,
                                     flags=(gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR))
        progress_dialog.set_deletable(False)
        label = gtk.Label(txt + "VPN")
        progress_dialog.vbox.pack_start(label, padding=10)
        progress_bar = gtk.ProgressBar()
        progress_bar.set_pulse_step(0.1)
        progress_dialog.vbox.pack_start(progress_bar, padding=10)
        gobject.timeout_add(250, self.progress_bar_timeout, progress_dialog,
                            proc, progress_bar, label)
        progress_dialog.show_all()
        progress_dialog.run()

    def progress_bar_timeout(self, progress_dialog, proc, progress_bar, label):
        logging.debug("progress_bar_timeout")

        progress_bar.pulse()
        if (proc.poll() != None):
            if (self.check_connected()):
                txt = "Connected to "
            else:
                txt = "Disconnected from "
            label.set_text(txt + "VPN.  Click the 'OK' button below to continue.")
            label.set_line_wrap(True)
            progress_dialog.vbox.remove(progress_bar)
            scrolled_window = gtk.ScrolledWindow()
            text_view = gtk.TextView()
            text_view.set_editable(False)
            text_view.set_cursor_visible(False)
            text_view.set_size_request(400, 200)
            buffer = text_view.get_buffer()
            buffer.set_text(proc.stdout.read())
            scrolled_window.add(text_view)
            progress_dialog.vbox.pack_start(scrolled_window)
            progress_dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
            progress_dialog.connect("response", self.dialog_response)
            progress_dialog.show_all()
            return False
        else:
            return True

    def username_changed(self, username):
        logging.debug("username_changed")
        self.username = username.get_text()

    def passwd_changed(self, passwd):
        logging.debug("passwd_changed")
        self.passwd = passwd.get_text()

    def username_prompt_activate(self, entry, dialog):
        logging.debug("username_prompt_activate")
        dialog.response(gtk.RESPONSE_ACCEPT)

    def username_prompt(self):
        logging.debug("username_prompt")

        username_prompt = gtk.Dialog(title=self.title,
                                  flags=(gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR),
                                  buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
                                          gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
        username = gtk.Entry(32)
        username.connect("changed", self.username_changed)
        username.connect("activate", self.username_prompt_activate, username_prompt)
        passwd = gtk.Entry(32)
        passwd.set_visibility(False)
        passwd.connect("changed", self.passwd_changed)
        passwd.connect("activate", self.username_prompt_activate, username_prompt)
        prompt = gtk.Label("Please enter your username and password:")
        prompt.set_line_wrap(True)
        table = gtk.Table(3, 2)
        table.attach(prompt, 0, 2, 0, 1, xpadding=5, ypadding=5)
        username_label = gtk.Label("Username (required):")
        table.attach(username_label, 0, 1, 1, 2, xpadding=5, ypadding=5)
        table.attach(username, 1, 2, 1, 2, xpadding=5, ypadding=5)
        passwd_label = gtk.Label("Password (required):")
        table.attach(passwd_label, 0, 1, 2, 3, xpadding=5, ypadding=5)
        table.attach(passwd, 1, 2, 2, 3, xpadding=5, ypadding=5)
        username_prompt.vbox.pack_start(table)
        username_prompt.connect("response", self.dialog_response)
        username_prompt.set_deletable(False)
        username_prompt.show_all()
        username_prompt.run()

gobject.type_register(VPN_Applet)

def vpn_factory(applet, iid):
    VPN_Applet(applet, iid)
    return gtk.TRUE

# run it in a gtk window
if len(sys.argv) > 1 and sys.argv[1] == "run-in-window":
    main_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
    main_window.set_title("VPN Applet")
    main_window.connect("destroy", gtk.mainquit)
    main_window.set_default_size(36, 36)
    app = gnomeapplet.Applet()
    vpn_factory(app, None)
    app.reparent(main_window)
    main_window.show_all()
    gtk.main()
    sys.exit()

if __name__ == '__main__':
    gnomeapplet.bonobo_factory("OAFIID:GNOME_VPN_Applet_Factory",
                               VPN_Applet.__gtype__,
                               "VPN Applet", "0.5", vpn_factory)

While there is plenty of Python and PyGTK documentation and examples, there is very little documentation on developing gnome applets using Python. Hopefully the code above will be helpful, in addition to the following links:

GNOME Documentation Library – PanelApplet
PyGTK 2.0 Reference Manual
PyGTK 2.0 Tutorial
Gnome applets with Python
Python Tutorial
subprocess – Subprocess Management

Adding Users via Web Services in Sakai
Tuesday, January 29th, 2008

We got ourselves into a little bind and had to add a few hundred users to our portfolio system in a short period of time. The alternatives were to figure out a way to script the addition of users or manually enter the students and add them to the appropriate worksite. Since I’m always up for a challenge, I decided to tackle the script…believe it or not I’ve never had the opportunity to work with perl and/or web services, so this was a great opportunity to get my hands dirty.

A quick search led me to Will Trillich’s helpful documentation over at Serensoft, which enabled me to get started pretty quickly. There was also some documentation over in the Sakai Confluence wiki that helped with the configuration. In order to use web services in sakai, you need to turn it on in the sakai.properties configuration file as follows:

# Enable web services login
webservices.allowlogin=true

If that line doesn’t exist, go ahead and add it.

Here’s the perl script I ended up with to add users from a csv file to sakai and enroll them in a specific course…play nice, it’s my first:


#!/usr/bin/perl

#
# Perl script to add a list of users to sakai and enroll
# them in a given course.  Users are read from a csv formatted
# file with lines like the following:
#
# SUID;DOB;LastName;FirstName;NETID
#

use SOAP::Lite;

# Sakai service location constants
use constant {
    LOGIN_WS => "/sakai-axis/SakaiLogin.jws?wsdl",
    SCRIPT_WS => "/sakai-axis/SakaiScript.jws?wsdl",
    SYR_ADDY => "\@syr.edu",
    DEFAULT_ROLE => "access",
};

# Initialize variables from command line parameters
my $userlist = shift;
my $host = shift;
my $id = shift;
my $password = shift;
my $siteId = shift;
my $roleId = shift;

# Routine to display some usage information
sub usage {
    print "\nUsage: useradd.pl filename host username password [siteId] [roleId]\n\n";
    print "e.g.  useradd.pl userlist.csv http://localhost:8080 admin admin_pass\n\n";
}

# Routine to connect to sakai login service
sub sakaiLogin {
    return $host.LOGIN_WS;
}

# Routine to connect to
sub sakaiScript {
    return $host.SCRIPT_WS;
}

# Routine to create an email address
sub makeEmail {
    my $username = shift;
    return $username.SYR_ADDY;
}

# Create password
sub makePassword {
    my $username = shift;
    my $dob = shift;
    ($month, $day, $year) = split(/\//, $dob);
    $month = ($month < 10) ? "0".$month : $month;
    $day = ($day < 10) ? "0".$day : $day;
    return $username.$month.$day;
}

# If either are undefined, display usage and exit
if (!$userlist ||
    !$host ||
    !$id ||
    !$password) {
    &usage;
    exit;
}

# Open the file containing the user list
open(USERLIST, $userlist) || die "Unable to open $userlist!\n";

# Initialize SOAP client
$loginClient = SOAP::Lite->new(proxy => &sakaiLogin($host));
$sessionId = $loginClient->login($id, $password)->result;

print "SESSION_ID: ".$sessionId."\n";

$scriptClient = SOAP::Lite->new(proxy => &sakaiScript($host));

while (<USERLIST>) {
    chop;
    ($suid, $dob, $lastName, $firstName, $netId) = split(/;/);
    my $email = &makeEmail($netId);
    my $password = &makePassword($netId, $dob);

    print "Attempting to add user:\n";
    print "\tEID:\t\t".$netId."\n";
    print "\tFIRST_NAME:\t".$firstName."\n";
    print "\tLAST_NAME:\t".$lastName."\n";
    print "\tEMAIL:\t\t".$email."\n";
    print "\tPASSWORD:\t".$password."\n";

    my $result = $scriptClient->addNewUser($sessionId,
					   $netId,
					   $firstName,
					   $lastName,
					   $email,
					   '',
					   $password)->result;

    print "Result: ".$result."\n";

    if ($siteId) {
	if (!$roleId) {
	    $roleId = DEFAULT_ROLE;
	}
	print "Attempting to add ".$netId." to site ".$siteId." with ".$roleId." role...";

	$result = $scriptClient->addMemberToSiteWithRole($sessionId,
							 $siteId,
							 $netId,
							 $roleId)->result;

	print $result."\n";
    }

    print "\n";
}

$loginClient->logout($sessionId);

close(USERLIST);

I was blown away how easy it was to accomplish so much with so little! In hindsight, there were several cases where I used bash scripts to do too much, whereas a simple perl script would have saved me a great deal of effort. I notice that there’s a pretty big dispute going on over which is better, perl or python…maybe next time I’ll give python a shot and let you know what I think.

Powered by Laughing Squid