mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 10:08:05 +00:00
Add support of realms in the authentication process through a global configuration variable
This commit is contained in:
parent
cda7864c52
commit
b6959cc5dd
11 changed files with 346 additions and 288 deletions
|
|
@ -10,6 +10,7 @@ APP_EVERYONE_IS_ADMIN=false
|
|||
# SIP server parameters
|
||||
ACCOUNT_PROXY_REGISTRAR_ADDRESS=sip.example.com # Proxy registrar address, can be different than the SIP domain
|
||||
ACCOUNT_TRANSPORT_PROTOCOL_TEXT="TLS (recommended), TCP or UDP" # Simple text, to explain how the SIP server can be reached
|
||||
ACCOUNT_REALM=null # Default realm for the accounts, fallback to the domain if not set, enforce null by default
|
||||
|
||||
# Instance specific parameters
|
||||
INSTANCE_COPYRIGHT= # Simple text displayed in the page footer
|
||||
|
|
|
|||
|
|
@ -94,14 +94,6 @@ If your external database is locate on a remote machine, you should also allow y
|
|||
setsebool httpd_can_network_connect 1 // Allow remote network connected
|
||||
setsebool httpd_can_network_connect_db 1 // Allow remote database connection
|
||||
|
||||
### CRON job
|
||||
|
||||
The DIGEST authentication method is saving some temporary information (nonces) in the database.
|
||||
|
||||
To expire and/or clear old nonces a specific command should be called periodically.
|
||||
|
||||
php artisan digest:expired-nonces-clear <minutes>
|
||||
|
||||
## Usage
|
||||
|
||||
The `/api` page contains all the required documentation to authenticate and request the API.
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class Account extends Authenticatable
|
|||
protected $connection = 'external';
|
||||
protected $with = ['passwords', 'admin', 'emailChanged'];
|
||||
protected $dateTimes = ['creation_time'];
|
||||
protected $appends = ['realm'];
|
||||
protected $casts = [
|
||||
'activated' => 'boolean',
|
||||
];
|
||||
|
|
@ -106,6 +107,11 @@ class Account extends Authenticatable
|
|||
return $this->attributes['username'].'@'.$this->attributes['domain'];
|
||||
}
|
||||
|
||||
public function getRealmAttribute()
|
||||
{
|
||||
return config('app.realm');
|
||||
}
|
||||
|
||||
public function requestEmailUpdate(string $newEmail)
|
||||
{
|
||||
// Remove all the old requests
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ class AccountController extends Controller
|
|||
$account = Account::sip($sip)->firstOrFail();
|
||||
|
||||
return \response()->json([
|
||||
'activated' => $account->activated
|
||||
'activated' => $account->activated,
|
||||
'realm' => $account->realm
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ class AuthenticateDigestOrKey
|
|||
->where('domain', $domain)
|
||||
->firstOrFail();
|
||||
|
||||
$resolvedRealm = config('app.realm') ?? $domain;
|
||||
|
||||
// Check if activated
|
||||
if (!$account->activated) {
|
||||
return $this->generateUnauthorizedResponse($account);
|
||||
|
|
@ -97,7 +99,7 @@ class AuthenticateDigestOrKey
|
|||
'opaque' => 'required|in:'.$this->getOpaque(),
|
||||
//'uri' => 'in:/'.$request->path(),
|
||||
'qop' => 'required|in:auth',
|
||||
'realm' => 'required|in:'.$domain,
|
||||
'realm' => 'required|in:'.$resolvedRealm,
|
||||
'nc' => 'required',
|
||||
'cnonce' => 'required',
|
||||
'algorithm' => [
|
||||
|
|
@ -126,8 +128,8 @@ class AuthenticateDigestOrKey
|
|||
|
||||
// Hashing and checking
|
||||
$A1 = $password->algorithm == 'CLRTXT'
|
||||
? hash($hash, $account->username.':'.$account->domain.':'.$password->password)
|
||||
: $password->password; // username:domain:password
|
||||
? hash($hash, $account->username.':'.$resolvedRealm.':'.$password->password)
|
||||
: $password->password; // username:realm/domain:password
|
||||
$A2 = hash($hash, $request->method().':'.$auth['uri']);
|
||||
|
||||
$validResponse = hash($hash,
|
||||
|
|
@ -194,20 +196,21 @@ class AuthenticateDigestOrKey
|
|||
private function generateAuthHeaders(Account $account, string $nonce): array
|
||||
{
|
||||
$headers = [];
|
||||
$resolvedRealm = config('app.realm') ?? $account->domain;
|
||||
|
||||
foreach ($account->passwords as $password) {
|
||||
if ($password->algorithm == 'CLRTXT') {
|
||||
foreach (array_keys(self::ALGORITHMS) as $algorithm) {
|
||||
array_push(
|
||||
$headers,
|
||||
$this->generateAuthHeader($account->domain, $algorithm, $nonce)
|
||||
$this->generateAuthHeader($resolvedRealm, $algorithm, $nonce)
|
||||
);
|
||||
}
|
||||
break;
|
||||
} else if (\in_array($password->algorithm, array_keys(self::ALGORITHMS))) {
|
||||
array_push(
|
||||
$headers,
|
||||
$this->generateAuthHeader($account->domain, $password->algorithm, $nonce)
|
||||
$this->generateAuthHeader($resolvedRealm, $password->algorithm, $nonce)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
551
flexiapi/composer.lock
generated
551
flexiapi/composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -24,6 +24,12 @@ return [
|
|||
'proxy_registrar_address' => env('ACCOUNT_PROXY_REGISTRAR_ADDRESS', 'sip.domain.com'),
|
||||
'transport_protocol_text' => env('ACCOUNT_TRANSPORT_PROTOCOL_TEXT', 'TLS (recommended), TCP or UDP'),
|
||||
|
||||
/**
|
||||
* Set a global realm for all the accounts, if not set, the account domain
|
||||
* will be used as a fallback
|
||||
*/
|
||||
'realm' => env('ACCOUNT_REALM', null),
|
||||
|
||||
/**
|
||||
* Allow any accounts to request the API as an administrator
|
||||
* This parameter is only the for debug purpose or running the tests
|
||||
|
|
|
|||
|
|
@ -33,10 +33,11 @@ class PasswordFactory extends Factory
|
|||
public function definition()
|
||||
{
|
||||
$account = Account::factory()->create();
|
||||
$realm = config('app.realm') ?? $account->domain;
|
||||
|
||||
return [
|
||||
'account_id' => $account->id,
|
||||
'password' => hash('md5', $account->username.':'.$account->domain.':testtest'),
|
||||
'password' => hash('md5', $account->username.':'.$realm.':testtest'),
|
||||
'algorithm' => 'MD5',
|
||||
];
|
||||
}
|
||||
|
|
@ -45,9 +46,10 @@ class PasswordFactory extends Factory
|
|||
{
|
||||
return $this->state(function (array $attributes) {
|
||||
$account = Account::find($attributes['account_id']);
|
||||
$realm = config('app.realm') ?? $account->domain;
|
||||
|
||||
return [
|
||||
'password' => hash('sha256', $account->username.':'.$account->domain.':testtest'),
|
||||
'password' => hash('sha256', $account->username.':'.$realm.':testtest'),
|
||||
'account_id' => $account->id,
|
||||
'algorithm' => 'SHA-256',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -219,13 +219,17 @@ class AccountApiTest extends TestCase
|
|||
$password->account->generateApiKey();
|
||||
$password->account->save();
|
||||
|
||||
$realm = 'realm.com';
|
||||
config()->set('app.realm', $realm);
|
||||
|
||||
/**
|
||||
* Public information
|
||||
*/
|
||||
$this->get($this->route.'/'.$password->account->identifier.'/info')
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'activated' => false
|
||||
'activated' => false,
|
||||
'realm' => $realm
|
||||
]);
|
||||
|
||||
$password->account->activated = true;
|
||||
|
|
@ -239,7 +243,8 @@ class AccountApiTest extends TestCase
|
|||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'username' => $password->account->username,
|
||||
'activated' => true
|
||||
'activated' => true,
|
||||
'realm' => $realm
|
||||
]);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ class AuthenticateDigestAndKeyTest extends TestCase
|
|||
public function testAuthenticationMD5()
|
||||
{
|
||||
$password = Password::factory()->create();
|
||||
|
||||
$response = $this->generateFirstResponse($password);
|
||||
$response = $this->generateSecondResponse($password, $response)
|
||||
->json($this->method, $this->route);
|
||||
|
|
@ -168,7 +169,7 @@ class AuthenticateDigestAndKeyTest extends TestCase
|
|||
public function testAuthenticationSHA265FromCLRTXT()
|
||||
{
|
||||
$password = Password::factory()->clrtxt()->create();
|
||||
$response = $this->generateFirstResponse($password);;
|
||||
$response = $this->generateFirstResponse($password);
|
||||
|
||||
// The server is generating all the available hash algorythms
|
||||
$this->assertStringContainsString('algorithm=MD5', $response->headers->all()['www-authenticate'][0]);
|
||||
|
|
@ -192,6 +193,32 @@ class AuthenticateDigestAndKeyTest extends TestCase
|
|||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testAuthenticationSHA265FromCLRTXTWithRealm()
|
||||
{
|
||||
$realm = 'realm.com';
|
||||
config()->set('app.realm', $realm);
|
||||
|
||||
$password = Password::factory()->clrtxt()->create();
|
||||
$response = $this->generateFirstResponse($password);
|
||||
|
||||
// Let's simulate a local hash for the clear password
|
||||
$hash = 'sha256';
|
||||
$password->password = hash(
|
||||
$hash,
|
||||
$password->account->username.':'.$realm.':'.$password->password
|
||||
);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'From' => 'sip:'.$password->account->identifier,
|
||||
'Authorization' => $this->generateDigest($password, $response, $hash),
|
||||
])->json($this->method, $this->route);
|
||||
|
||||
$this->assertStringContainsString('algorithm=MD5', $response->headers->all()['www-authenticate'][0]);
|
||||
$this->assertStringContainsString('algorithm=SHA-256', $response->headers->all()['www-authenticate'][1]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testAuthenticationBadPassword()
|
||||
{
|
||||
$password = Password::factory()->create();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#%define _datadir %{_datarootdir}
|
||||
#%define _docdir %{_datadir}/doc
|
||||
|
||||
%define build_number 44
|
||||
%define build_number 45
|
||||
%define var_dir /var/opt/belledonne-communications
|
||||
%define opt_dir /opt/belledonne-communications/share/flexisip-account-manager
|
||||
%define env_file "$RPM_BUILD_ROOT/etc/flexisip-account-manager/flexiapi.env"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue