Open Source Billing

This commit is contained in:
Deon George
2013-10-10 13:44:53 +11:00
commit b02d70adf0
2344 changed files with 392978 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides Admin SSL functions
*
* @package SSL
* @category Controllers/Admin
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Controller_Admin_SSL extends Controller_TemplateDefault_Admin {
protected $secure_actions = array(
'add'=>TRUE,
'list'=>TRUE,
'view'=>TRUE,
);
public function action_list() {
Block::add(array(
'title'=>_('SSL CA Certificates'),
'body'=>Table::display(
ORM::factory('SSL_CA')->find_all(),
25,
array(
'id'=>array('label'=>'ID','url'=>URL::link('admin','ssl/view/')),
'sign_cert'=>array('label'=>'Cert'),
'issuer()'=>array('label'=>'Issuer'),
'valid_to(TRUE)'=>array('label'=>'Expires'),
),
array(
'page'=>TRUE,
'type'=>'select',
'form'=>URL::link('admin','ssl/view'),
)),
));
}
private function add_view($id=NULL,$output='') {
$so = ORM::factory('SSL_CA',$id);
if ($_POST) {
if ($so->values($_POST)->changed()) {
try {
$so->save();
SystemMessage::add(array(
'title'=>'SSL Certificate Saved',
'type'=>'info',
'body'=>'SSL Certificate successfully recorded.',
));
} catch (ORM_Validation_Exception $e) {
$errors = $e->errors('models');
SystemMessage::add(array(
'title'=>'SSL Certificate NOT saved',
'type'=>'error',
'body'=>join("\n",array_values($errors)),
));
$so->reload();
}
}
}
$output .= Form::open();
$output .= View::factory('ssl/admin/add_view')
->set('o',$so);
$output .= Form::submit('submit','submit',array('class'=>'form_button'));
$output .= Form::close();
return $output;
}
public function action_add() {
Block::add(array(
'title'=>_('Add SSL CA Certificate'),
'body'=>$this->add_view(),
));
}
public function action_view() {
list($id,$output) = Table::page(__METHOD__);
Block::add(array(
'title'=>sprintf('%s: %s (%s)',_('View SSL CA Certificate'),$id,ORM::factory('SSL_CA',$id)->display('sign_cert')),
'body'=>$this->add_view($id,$output),
));
Block::add(array(
'title'=>_('Services using this Certificate'),
'body'=>Table::display(
ORM::factory('SSL_CA',$id)->list_issued(),
25,
array(
'id'=>array('label'=>'ID','url'=>URL::link('admin','service/view/')),
'plugin()->dn()'=>array('label'=>'Cert'),
'plugin()->valid_to(TRUE)'=>array('label'=>'Expires'),
),
array(
'page'=>TRUE,
'type'=>'select',
'form'=>URL::link('admin','service/view'),
)),
));
}
}
?>

View File

@@ -0,0 +1,50 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides User SSL functions
*
* @package SSL
* @category Controllers/User
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Controller_User_SSL extends Controller_TemplateDefault_User {
protected $secure_actions = array(
'download'=>FALSE,
);
public function action_download() {
$id = $_POST['sid'];
$so = ORM::factory('Service',$id);
if (! $so->loaded())
HTTP::redirect('welcome/index');
$passwd = $_POST['passwd'];
if (strlen($passwd) < Kohana::$config->load('ssl')->minpass_length) {
SystemMessage::add(array(
'title'=>_('Validation failed'),
'type'=>'error',
'body'=>_('Your requested password is too short.'),
));
HTTP::redirect(URL::link('user','service/view/'.$so->id));
}
if (! $so->loaded() OR ! Auth::instance()->authorised($so->account)) {
$this->template->content = 'Unauthorised or doesnt exist?';
return FALSE;
}
$file = sprintf('%s/%s.pkcs12',Kohana::$config->load('config')->tmpdir,$so->name());
openssl_pkcs12_export_to_file($so->plugin()->cert,$file,$so->plugin()->pk,$passwd,array('extracerts'=>$so->plugin()->cacerts()));
$x = file_get_contents($file);
unlink($file);
$this->response->headers('Content-Type','application/pks12');
$this->response->headers('Content-Disposition','attachment; filename="'.basename($file).'"');
$this->response->body($x);
$this->auto_render = FALSE;
}
}
?>

View File

@@ -0,0 +1,24 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class supports SSL products
*
* @package SSL
* @category Models
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Model_Product_Plugin_Ssl extends Model_Product_Plugin {
protected $_table_name = 'ssl';
// Our required abstract methods
public function admin_update() {}
public function feature_summary() {
// @todo This view should render based on the the results of this::allowance();
return View::factory('product/plugin/ssl/feature_summary')
->set('po',$this);
}
}
?>

View File

@@ -0,0 +1,15 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class supports SSL
*
* @package SSL
* @category Models
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Model_SSL extends ORM_OSB {
protected $_updated_column = FALSE;
}
?>

View File

@@ -0,0 +1,96 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class supports SSL
*
* @package SSL
* @category Models
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Model_SSL_CA extends ORM_OSB {
protected $_updated_column = FALSE;
// Relationships
protected $_has_many = array(
'service'=>array('through'=>'service__ssl'),
);
protected $_display_filters = array(
'sign_cert'=>array(
array('SSL::subject',array(':value')),
),
);
public function rules() {
return array(
'sign_cert'=>array(
array(array($this,'isCert')),
array(array($this,'isCA')),
),
'parent_ssl_ca_id'=>array(
array(array($this,'Rule_ParentExists')),
),
);
}
public function filters() {
return array(
'parent_ssl_ca_id'=>array(
array(array($this,'Filter_GetParent')),
)
);
}
private $_so = NULL;
/**
* Resolve any queries to certificate details
*/
public function __call($name,$args) {
$m = 'get_'.$name;
if (method_exists($this->_so,$m))
return $this->_so->{$m}($args);
else
throw new Kohana_Exception('Unknown method :method',array(':method'=>$name));
}
// We want to inject the SSL object into this Model
protected function _load_values(array $values) {
parent::_load_values($values);
if ($this->sign_cert)
$this->_so = SSL::instance($this->sign_cert);
return $this;
}
// If we change the SSL certificate, we need to reload our SSL object
public function values(array $values, array $expected = NULL) {
parent::values($values,$expected);
if (array_key_exists('sign_cert',$values))
$this->_so = SSL::instance($this->sign_cert);
return $this;
}
// @todo This could require some optimisation, by storing the keyid in the database and then getting the DB just to return that parent
public function Filter_GetParent() {
foreach (ORM::factory($this->_object_name)->find_all() as $sco)
if ($sco->aki_keyid() == $this->aki_keyid())
return $sco->id;
}
public function Rule_ParentExists() {
// Our parent_ssl_ca_id should have been populated by Filter_GetParent().
return $this->parent_ssl_ca_id OR $this->isRoot();
}
public function list_issued() {
return $this->service->find_all();
}
}
?>

View File

@@ -0,0 +1,169 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class supports Services
*
* @package SSL
* @category Models
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Model_Service_Plugin_Ssl extends Model_Service_Plugin {
protected $_table_name = 'service__ssl';
protected $_updated_column = FALSE;
// Relationships
protected $_belongs_to = array(
'service'=>array(),
);
protected $_has_one = array(
'SSL_CA'=>array('far_key'=>'ssl_ca_id','foreign_key'=>'id'),
);
protected $_display_filters = array(
'csr'=>array(
array('SSL::csrsubject',array(':value')),
),
'cert'=>array(
array('SSL::subject',array(':value')),
),
);
// Required abstract functions
public function username_value() {} // Not used
public function password_value() {} // Not used
private $_so = NULL;
/**
* Resolve any queries to certificate details
*/
public function __call($name,$args) {
$m = 'get_'.$name;
if (method_exists($this->_so,$m))
return $this->_so->{$m}($args);
else
throw new Kohana_Exception('Unknown method :method',array(':method'=>$name));
}
// We want to inject the SSL object into this Model
protected function _load_values(array $values) {
parent::_load_values($values);
if ($this->cert)
$this->_so = SSL::instance($this->cert);
return $this;
}
// If we change the SSL certificate, we need to reload our SSL object
public function values(array $values, array $expected = NULL) {
parent::values($values,$expected);
if (array_key_exists('cert',$values))
$this->_so = SSL::instance($this->cert);
return $this;
}
public function expire() {
return $this->_so->get_valid_to();
}
public function name() {
return ($this->cert AND $this->SSL_CA->loaded()) ? sprintf('%s:%s',$this->SSL_CA->subject(),$this->display('cert')) : $this->display('csr');
}
public function service_view() {
return View::factory('service/user/plugin/ssl/view')
->set('so',$this);
}
/**
* Get specific service details for use in other modules
* For Example: Invoice
*
* @todo Make the rendered items configurable
* @todo Change this method name, now that it is public
*/
// @todo This needs to be validated for this model
public function _details($type) {
switch ($type) {
case 'invoice_detail_items':
return array();
break;
default:
return parent::$_details($type);
}
}
// @todo This needs to be validated for this model
public function admin_update() {
return View::factory('service/admin/plugin/ssl/update')
->set('mediapath',Route::get('default/media'))
->set('so',$this);
}
public function download_button() {
if (! $this->service->status OR ! preg_match('/client/',$this->service->product->plugin()->extensions) OR $this->valid_to() < time())
return '';
// @todo Do some password validation
$output = Form::open(URL::link('user','ssl/download'));
$output .= Form::hidden('sid',$this->service->id);
$output .= _('Choose a password').': '.Form::password('passwd','').'<br/><br/>';
$output .= Form::submit('download','Download',array('class'=>'form_button'));
return $output;
}
public function cacerts() {
$result = array();
$x = $this->ssl_ca_id;
while ($x) {
$sco = ORM::factory('SSL_CA',$x);
array_push($result,$sco->sign_cert);
$x = $sco->parent_ssl_ca_id;
}
return $result;
}
public function renew() {
$d = SSL::instance($this->cert);
$ssl_conf = Kohana::$config->load('ssl');
// @todo change this so an admin can force this.
$force = TRUE;
// If our certificate is not old enough skip
if ($d->get_valid_to() > time()+$ssl_conf['min_renew_days']*86400 AND ! $force)
return FALSE;
$res = openssl_csr_sign($this->csr,$this->SSL_CA->sign_cert,$this->SSL_CA->sign_pk,$this->service->product->plugin()->days,array(
'config'=>$ssl_conf['config'],
'x509_extensions'=>$this->service->product->plugin()->extensions,
'digest_alg'=>'sha1',
),time());
if ($res AND openssl_x509_export($res,$cert)) {
$this->cert = $cert;
$this->save();
return TRUE;
} else {
print_r(array(
'csr'=>$this->csr,
'ca'=>$this->SSL_CA->sign_cert,
'capk'=>$this->SSL_CA->sign_pk,
'days'=>$this->service->product->plugin()->days,
'ssl'=>$ssl_conf,
'x509e'=>$this->service->product->plugin()->extensions
));
throw new Kohana_Exception('Error Creating SSL Certificate :error',array(':error'=>openssl_error_string()));
}
}
}
?>

218
modules/ssl/classes/SSL.php Normal file
View File

@@ -0,0 +1,218 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class is for access to SSL information
*
* @package SSL
* @category Helpers
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class SSL {
private $cert = '';
private $_details = array();
public function __construct($cert) {
$this->cert = $cert;
}
public static function instance($cert) {
return new SSL($cert);
}
/**
* This function will convert a large decimal number into hex
* @param $number Large decimal number
*/
private static function _dec_to_hex($number) {
$hex = array();
if ($number == 0)
return '00';
while ($number > 0) {
if ($number == 0) {
array_push($hex, '0');
} else {
$x = (int) ($number/16);
array_push($hex,strtoupper(dechex((int)($number-($x*16)))));
$number = $x;
}
}
return preg_replace('/^:/','',preg_replace('/(..)/',":$1",implode(array_reverse($hex))));
}
/**
* Parse our AuthorityKeyIndentifier Extension to extract information
* @param $key Return just that index
*/
private function _aki($key=NULL) {
$result = array();
$aki = $this->_extensions('authorityKeyIdentifier');
if (! $aki)
return '';
foreach (explode("\n",preg_replace("/\n$/",'',$aki)) as $x) {
if (! $x)
continue;
if (strstr($x,':')) {
list($a,$b) = explode(':',$x,2);
$result[strtolower($a)] = $b;
}
}
return is_null($key) ? $result : (isset($result[$key]) ? $result[$key] : '');
}
private function _bc() {
return $this->_extensions('basicConstraints');
}
/**
* Parse our Sign Certifcate to extract information
* @param $key Return just that index
*/
private function _details($key=NULL) {
if (! $this->cert)
return array();
if (! $this->_details)
$this->_details = openssl_x509_parse($this->cert);
return is_null($key) ? $this->_details : (isset($this->_details[$key]) ? $this->_details[$key] : array());
}
/**
* Parse our Sign Certifcate Extensions to extract information
* @param $key Return just that index
*/
private function _extensions($key=NULL) {
$result = $this->_details('extensions');
return is_null($key) ? $result : (isset($result[$key]) ? $result[$key] : '');
}
/**
* Render a DN array as a string
*/
private function _dn(array $array) {
$result = '';
$i = 0;
foreach ($array as $k=>$v) {
if ($i++)
$result .= ',';
$result .= sprintf('%s=%s',$k,$v);
}
return $result;
}
public function get_aki_dirname() {
return $this->_aki('dirname');
}
public function get_aki_keyid() {
return $this->_aki('keyid');
}
public function get_aki_serial() {
return $this->_aki('serial');
}
public function get_algorithm() {
$e = '';
openssl_x509_export(openssl_x509_read($this->cert),$e,FALSE);
// @todo There must be a nice way to get this?
return (preg_match('/^\s+Signature Algorithm:\s*(.*)\s*$/m',$e,$match)) ? $match[1] : _('Unknown');
}
public function get_ca_path_len() {
$m = array();
$x = preg_match('/.*pathlen:\s*([0-9]+).*$/',$this->_bc(),$m);
return isset($m[1]) ? (int)$m[1] : 0;
}
public function get_dn() {
return $this->_dn($this->_details('subject'));
}
public function get_hash() {
return $this->_details('hash');
}
public function get_isCA() {
return preg_match('/CA:TRUE/',$this->_bc());
}
public function get_isCert() {
return is_array($this->_details());
}
public function get_isRoot() {
return $this->get_aki_keyid() == $this->get_ski();
}
public function get_issuer() {
$k = $this->_details('issuer');
return isset($k['CN']) ? $k['CN'] : '';
}
public function get_issuerdn() {
return $this->_dn($this->_details('issuer'));
}
public function get_serial() {
return $this->_dec_to_hex($this->_details('serialNumber'));
}
public function get_subject() {
$k = $this->_details('subject');
return isset($k['CN']) ? $k['CN'] : '';
}
public function get_ski() {
return $this->_extensions('subjectKeyIdentifier');
}
public function get_valid_to($format=FALSE) {
$k = $this->_details('validTo_time_t');
return $format ? Config::date($k) : $k;
}
public function get_valid_from($format=FALSE) {
$k = $this->_details('validFrom_time_t');
return $format ? Config::date($k) : $k;
}
public function get_version() {
return $this->_details('version');
}
public static function xexpire($cert,$format=FALSE) {
return static::instance($cert)->get_expire($format);
}
public static function subject($cert) {
return static::instance($cert)->get_subject();
}
public static function csrsubject($csr) {
$c = openssl_csr_get_subject($csr);
return $c['CN'];
}
}
?>

View File

@@ -0,0 +1,21 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* Renew an SSL Certificate
*
* @package SSL
* @category Tasks
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Task_SSL_Renew extends Task {
/**
* Renew a certificate
*/
protected function _execute(array $params) {
// @todo, Change this to be a SSL id, maybe list all the certs expiring
ORM::factory('Service',$params['id'])->plugin()->renew();
}
}
?>