1 package FS::part_export::aradial;
3 use base qw( FS::part_export );
9 use Date::Format 'time2str';
11 use vars qw( %options %info $me $DEBUG );
14 $me = '[FS::part_export::aradial]';
17 tie %options, 'Tie::IxHash',
18 'port' => { label => 'HTTP port', default => 8000 },
19 'login' => { label => 'Admin username' },
20 'pass' => { label => 'Admin password' },
21 'realm' => { label => 'Admin authentication realm' },
22 'group' => { label => 'Group name' },
27 'desc' => 'Export accounts to Aradial RADIUS HTTP interface',
28 'options' => \%options,
31 <p>This export maintains user accounts on an Aradial Technologies access
32 control server, via the HTTP interface. The export hostname and the
33 <i>HTTP port</i> option determine the location of the server.</p>
34 <p><i>Admin username, password, authentication realm</i> are the settings
35 for the HTTP interface, set in the "Admin Web Interface Security" options
36 for your Aradial server.</p>
37 <p><i>Group name</i> is the user group to assign to new users, and must
38 already exist on the Aradial server. Currently this export will assign
39 all users to a single group; if you want multiple groups for different
40 service types, create another export instance.</p>
45 my ($self, $svc) = @_;
46 my $result = $self->request_user_edit(
48 $self->svc_acct_params($svc),
49 'db_$N$Users.Status' => 0,
51 if ($svc->cust_svc->cust_pkg->susp > 0 ) {
52 $result ||= $self->export_suspend($svc);
58 my ($self, $new, $old) = @_;
59 if ($new->email ne $old->email) {
60 return $old->export_delete || $new->export_insert;
63 $Status = 3 if $new->cust_svc->cust_pkg->susp > 0;
64 $self->request_user_edit(
67 'UserID' => $old->email,
68 $self->svc_acct_params($new),
69 'db_$N$Users.Status' => $Status,
74 my ($self, $svc) = @_;
75 $self->request_user_edit(
77 'UserID' => $svc->email,
78 'db_$N$Users.Status' => '3',
82 sub _export_unsuspend {
83 my ($self, $svc) = @_;
84 $self->request_user_edit(
86 'UserID' => $svc->email,
87 'db_$N$Users.Status' => 0,
92 my ($self, $svc) = @_;
93 $self->request_user_edit(
95 ('$Checked$' . $svc->email) => 1,
99 # Send a request to the 'UserEdit' interface, and process the response into
100 # an error string (empty on success, per Freeside convention).
102 sub request_user_edit {
103 my ($self, @params) = @_;
104 my $result = eval { $self->request( Page => 'UserEdit', @params ) };
105 return $result unless ref($result);
106 my $status = $result->findvalue('Result/Status/@value'); # XPath
107 if ($status eq 'Success') {
110 my $error = $result->findvalue('Result/Reason/@value')
112 return "updating Aradial user database: $error";
116 # Send a request to any interface, parse the response (from XML), and
117 # return it (as an XML::LibXML::Document). Returns a string if there's an
123 my $path = '/ArdWeb/ARDAdminIs.dll'; # I think this is always right
124 my $url = URI->new('http://' . $self->host . $path);
125 warn "$me request: \n".Dumper(\@params)."\n\n" if $DEBUG >= 2;
126 my $response = $self->ua->post($url, \@params);
127 if ( $response->is_success ) {
128 my $content = $response->decoded_content;
129 warn "$me response: \n$content\n\n" if $DEBUG >= 2;
130 return $self->parser->parse_string($content);
131 # the formats of these are _variable_.
132 # Some of them have a <Result><Status value="Success"><Entity ... >
133 # kind of structure, but not all. They do all seem to be XML, though.
135 return "API request error: ".$response->status_line;
139 sub svc_acct_params {
142 my $pkg = $svc->cust_svc->cust_pkg;
143 my $cust = $pkg->cust_main;
144 my $location = $pkg->cust_location;
145 # should we use the package contact's name/phone here?
147 my $setup_date = time2str('D%Y-%m-%d',
148 ($pkg->setup || $pkg->start_date || time)
150 my $expire_date = $pkg->expire ? time2str('D%Y-%m-%d', $pkg->expire) : '';
153 'db_Users.UserID' => $svc->email,
154 $self->password_params($svc),
155 'db_$D$Users.StartDate' => $setup_date,
156 'db_$D$Users.UserExpiryDate' => $expire_date,
157 'db_$RS$Users.GroupName' => $self->option('group'),
158 'db_$I$Users.UserIP' => $svc->slipip,
159 'db_UserDetails.FirstName' => $cust->first,
160 'db_UserDetails.LastName' => $cust->last,
161 'db_UserDetails.Company' => $cust->company,
162 'db_UserDetails.Email' => $cust->invoicing_list_emailonly_scalar,
163 'db_UserDetails.Address1' => $location->address1,
164 'db_UserDetails.Address2' => $location->address2,
165 'db_UserDetails.City' => $location->city,
166 'db_%GS%UserDetails.State' => $location->state,
167 'db_%GS%UserDetails.Country' => $location->country,
168 'db_UserDetails.Zip' => $location->zip,
169 'db_UserDetails.PhoneHome' => ($cust->daytime || $cust->night || $cust->mobile),
170 'db_UserDetails.PhoneFax' => $cust->fax,
174 sub password_params {
175 my ($self, $svc) = @_;
177 my $password_encryption = 0;
178 my $password = $svc->_password;
179 if ($svc->_password_encoding eq 'crypt') {
180 if ($svc->_password_encryption eq 'des') {
181 $password_encryption = 2;
182 } elsif ( $svc->_password_encryption eq 'md5') {
183 $password_encryption = 5;
185 } elsif ( $svc->_password_encoding eq 'ldap' ) {
186 $svc->_password =~ /^\{([\w-]+)\}(.*)$/;
189 $password_encryption = 7;
190 } elsif ($1 eq 'SHA' or $1 eq 'SHA-1') {
191 $password_encryption = 1;
194 ( Password => $password,
195 PasswordEncryptionType => $password_encryption
199 # return the XML parser
202 $self->{_parser} ||= XML::LibXML->new;
205 # return hostname:port
208 $self->machine . ':' . $self->option('port');
211 # return the LWP::UserAgent object
214 $self->{_ua} ||= do {
215 my $ua = LWP::UserAgent->new;
218 $self->option('realm'),
219 $self->option('login'),
220 $self->option('pass')