Merge pull request #543 from arabcoders/dev
Initial support for adding plex users with PIN.
This commit is contained in:
@@ -173,5 +173,11 @@ return [
|
||||
'visible' => false,
|
||||
'description' => 'Whether the token has limited access.',
|
||||
],
|
||||
[
|
||||
'key' => 'options.PLEX_USER_PIN',
|
||||
'type' => 'int',
|
||||
'visible' => false,
|
||||
'description' => 'Plex user PIN.',
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -175,6 +175,16 @@
|
||||
<NuxtLink @click="getUsers" v-text="'Retrieve User ids from backend.'" v-if="stage < 4"/>
|
||||
</p>
|
||||
</div>
|
||||
<template v-if="'plex' === backend.type">
|
||||
<label class="label">User PIN</label>
|
||||
<div class="control has-icons-left">
|
||||
<input class="input" type="text" v-model="backend.options.PLEX_USER_PIN" :disabled="stage > 3">
|
||||
<div class="icon is-left"><i class="fas fa-key"></i></div>
|
||||
<p class="help">
|
||||
If the selected user is using <code>PIN</code> to login, enter the PIN here.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template v-if="stage >= 4">
|
||||
@@ -272,11 +282,17 @@
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
|
||||
<div class="card-footer-item" v-if="stage >= 1">
|
||||
<button class="button is-fullwidth is-warning" type="button" @click="stage = stage-1">
|
||||
<span class="icon"><i class="fas fa-arrow-left"></i></span>
|
||||
<span>Previous Step</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card-footer-item" v-if="stage < maxStages">
|
||||
<button class="button is-fullwidth is-primary" type="submit" @click="changeStep()">
|
||||
<span class="icon">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</span>
|
||||
<button class="button is-fullwidth is-info" type="submit" @click="changeStep()">
|
||||
<span class="icon"><i class="fas fa-arrow-right"></i></span>
|
||||
<span>Next Step</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -500,10 +516,6 @@ onMounted(async () => {
|
||||
backend.value.type = supported.value[0]
|
||||
})
|
||||
|
||||
watch(stage, v => {
|
||||
console.log(v);
|
||||
}, {immediate: true})
|
||||
|
||||
const changeStep = async () => {
|
||||
let _
|
||||
|
||||
|
||||
@@ -590,6 +590,12 @@ const getServers = async () => {
|
||||
url: window.location.origin,
|
||||
};
|
||||
|
||||
if (backend.value?.options && backend.value.options?.ADMIN_TOKEN) {
|
||||
data.options = {
|
||||
ADMIN_TOKEN: backend.value.options.ADMIN_TOKEN
|
||||
}
|
||||
}
|
||||
|
||||
const response = await request(`/backends/discover/${backend.value.type}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Backends\Plex\PlexClient;
|
||||
use App\Libs\Attributes\Route\Get;
|
||||
use App\Libs\Enums\Http\Status;
|
||||
use App\Libs\Exceptions\InvalidArgumentException;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\Traits\APITraits;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||
@@ -42,7 +43,14 @@ final class Discover
|
||||
|
||||
assert($client instanceof PlexClient);
|
||||
|
||||
$list = $client::discover($this->http, $client->getContext()->backendToken);
|
||||
$context = $client->getContext();
|
||||
$opts = [];
|
||||
|
||||
if (null !== ($adminToken = ag($context->options, Options::ADMIN_TOKEN))) {
|
||||
$opts[Options::ADMIN_TOKEN] = $adminToken;
|
||||
}
|
||||
|
||||
$list = $client::discover($this->http, $context->backendToken, $opts);
|
||||
return api_response(Status::OK, ag($list, 'list', []));
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return api_error($e->getMessage(), Status::NOT_FOUND);
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Libs\Attributes\Route\Route;
|
||||
use App\Libs\DataUtil;
|
||||
use App\Libs\Enums\Http\Status;
|
||||
use App\Libs\Exceptions\InvalidArgumentException;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\Traits\APITraits;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||
@@ -42,7 +43,13 @@ final class Discover
|
||||
}
|
||||
|
||||
try {
|
||||
$list = $client::discover($this->http, $client->getContext()->backendToken);
|
||||
$opts = [];
|
||||
|
||||
if (null !== ($adminToken = ag($request->getParsedBody(), 'options.' . Options::ADMIN_TOKEN))) {
|
||||
$opts[Options::ADMIN_TOKEN] = $adminToken;
|
||||
}
|
||||
|
||||
$list = $client::discover($this->http, $client->getContext()->backendToken, $opts);
|
||||
return api_response(Status::OK, ag($list, 'list', []));
|
||||
} catch (Throwable $e) {
|
||||
return api_error($e->getMessage(), Status::INTERNAL_SERVER_ERROR);
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Backends\Common\Error;
|
||||
use App\Backends\Common\Levels;
|
||||
use App\Backends\Common\Response;
|
||||
use App\Libs\Container;
|
||||
use App\Libs\Options;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\RetryableHttpClient;
|
||||
@@ -71,6 +72,10 @@ final class GetUserToken
|
||||
'url' => (string)$url,
|
||||
]);
|
||||
|
||||
if (null !== ($pin = ag($context->options, Options::PLEX_USER_PIN))) {
|
||||
$url = $url->withQuery(http_build_query(['pin' => $pin]));
|
||||
}
|
||||
|
||||
$response = $this->http->request('POST', (string)$url, [
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
@@ -83,13 +88,14 @@ final class GetUserToken
|
||||
return new Response(
|
||||
status: false,
|
||||
error: new Error(
|
||||
message: 'Request for temporary access token for [{backend}] user [{username}] failed due to rate limit. error 429.',
|
||||
message: "Request for temporary access token for '{backend}' user '{username}'{pin} failed due to rate limit. error 429.",
|
||||
context: [
|
||||
'backend' => $context->backendName,
|
||||
'username' => $username,
|
||||
'user_id' => $userId,
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'headers' => $response->getHeaders(),
|
||||
'pin' => null !== $pin ? ' with pin' : '',
|
||||
],
|
||||
level: Levels::ERROR
|
||||
),
|
||||
@@ -100,13 +106,14 @@ final class GetUserToken
|
||||
return new Response(
|
||||
status: false,
|
||||
error: new Error(
|
||||
message: 'Request for [{backend}] user [{username}] temporary access token responded with unexpected [{status_code}] status code.',
|
||||
message: "Request for '{backend}' user '{username}'{pin} temporary access token responded with unexpected '{status_code}' status code.",
|
||||
context: [
|
||||
'backend' => $context->backendName,
|
||||
'username' => $username,
|
||||
'user_id' => $userId,
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'headers' => $response->getHeaders(),
|
||||
'pin' => null !== $pin ? ' with pin' : '',
|
||||
],
|
||||
level: Levels::ERROR
|
||||
),
|
||||
@@ -120,13 +127,14 @@ final class GetUserToken
|
||||
);
|
||||
|
||||
if ($context->trace) {
|
||||
$this->logger->debug('Parsing temporary access token for [{backend}] user [{username}] payload.', [
|
||||
$this->logger->debug("Parsing temporary access token for '{backend}' user '{username}'{pin} payload.", [
|
||||
'backend' => $context->backendName,
|
||||
'username' => $username,
|
||||
'user_id' => $userId,
|
||||
'url' => (string)$url,
|
||||
'trace' => $json,
|
||||
'headers' => $response->getHeaders(),
|
||||
'pin' => null !== $pin ? ' with pin' : '',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -141,11 +149,12 @@ final class GetUserToken
|
||||
])
|
||||
);
|
||||
|
||||
$this->logger->debug('Requesting permanent access token for [{backend}] user [{username}].', [
|
||||
$this->logger->debug("Requesting permanent access token for '{backend}' user '{username}'{pin}.", [
|
||||
'backend' => $context->backendName,
|
||||
'username' => $username,
|
||||
'user_id' => $userId,
|
||||
'url' => (string)$url,
|
||||
'pin' => null !== $pin ? ' with pin' : '',
|
||||
]);
|
||||
|
||||
$response = $this->http->request('GET', (string)$url, [
|
||||
@@ -163,12 +172,13 @@ final class GetUserToken
|
||||
);
|
||||
|
||||
if ($context->trace) {
|
||||
$this->logger->debug('Parsing permanent access token for [{backend}] user [{username}] payload.', [
|
||||
$this->logger->debug("Parsing permanent access token for '{backend}' user '{username}'{pin} payload.", [
|
||||
'backend' => $context->backendName,
|
||||
'username' => $username,
|
||||
'user_id' => $userId,
|
||||
'url' => (string)$url,
|
||||
'trace' => $json,
|
||||
'pin' => null !== $pin ? ' with pin' : '',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -189,7 +199,7 @@ final class GetUserToken
|
||||
}
|
||||
|
||||
$this->logger->error(
|
||||
'Response had [{count}] associated servers, non match [{backend} - [{backend_id}] unique identifier.',
|
||||
"Response had '{count}' associated servers, non match '{backend}: {backend_id}' unique identifier.",
|
||||
[
|
||||
'count' => count(($json)),
|
||||
'backend' => $context->backendName,
|
||||
@@ -201,11 +211,12 @@ final class GetUserToken
|
||||
return new Response(
|
||||
status: false,
|
||||
error: new Error(
|
||||
message: 'No permanent access token was found for [{username}] in [{backend}] response. Likely invalid unique identifier was selected or plex.tv API error, check https://status.plex.tv or try running same command with [--debug] flag for more information.',
|
||||
message: "No permanent access token was found for '{username}'{pin} in '{backend}' response. Likely invalid unique identifier was selected or plex.tv API error, check https://status.plex.tv or try running same command with [--debug] flag for more information.",
|
||||
context: [
|
||||
'backend' => $context->backendName,
|
||||
'username' => $username,
|
||||
'user_id' => $userId,
|
||||
'pin' => null !== $pin ? ' with pin' : '',
|
||||
],
|
||||
level: Levels::ERROR
|
||||
),
|
||||
@@ -214,10 +225,11 @@ final class GetUserToken
|
||||
return new Response(
|
||||
status: false,
|
||||
error: new Error(
|
||||
message: 'Exception [{error.kind}] was thrown unhandled during [{client}: {backend}] request for [{username}] access token. Error [{error.message} @ {error.file}:{error.line}].',
|
||||
message: "Exception '{error.kind}' was thrown unhandled during '{client}: {backend}' request for '{username}'{pin} access token. Error '{error.message}' at '{error.file}:{error.line}'.",
|
||||
context: [
|
||||
'backend' => $context->backendName,
|
||||
'client' => $context->clientName,
|
||||
'pin' => isset($pin) ? ' with pin' : '',
|
||||
'error' => [
|
||||
'kind' => $e::class,
|
||||
'line' => $e->getLine(),
|
||||
|
||||
@@ -33,6 +33,7 @@ use App\Libs\Config;
|
||||
use App\Libs\Container;
|
||||
use App\Libs\DataUtil;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Enums\Http\Status;
|
||||
use App\Libs\Exceptions\Backends\RuntimeException;
|
||||
use App\Libs\Exceptions\HttpException;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
@@ -671,28 +672,38 @@ class PlexClient implements iClient
|
||||
|
||||
$payload = $response->getContent(false);
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
if (Status::OK !== Status::from($response->getStatusCode())) {
|
||||
if (Status::UNAUTHORIZED === Status::from($response->getStatusCode())) {
|
||||
if (null !== ($adminToken = ag($opts, Options::ADMIN_TOKEN))) {
|
||||
$opts['with_admin'] = true;
|
||||
return self::discover($http, $adminToken, ag_delete($opts, Options::ADMIN_TOKEN));
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException(
|
||||
r(
|
||||
text: 'PlexClient: Request for servers list returned with unexpected [{status_code}] status code. {context}',
|
||||
text: "PlexClient: Request for servers list returned with unexpected '{status_code}' status code. {context}",
|
||||
context: [
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'context' => arrayToString(['payload' => $payload]),
|
||||
'context' => arrayToString([
|
||||
'with_admin' => true === ag($opts, 'with_admin'),
|
||||
'payload' => $payload
|
||||
]),
|
||||
]
|
||||
)
|
||||
), $response->getStatusCode()
|
||||
);
|
||||
}
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
throw new RuntimeException(
|
||||
r(
|
||||
text: 'PlexClient: Exception [{kind}] was thrown unhandled during request for plex servers list, likely network related error. [{error} @ {file}:{line}]',
|
||||
text: "PlexClient: Exception '{kind}' was thrown unhandled during request for plex servers list, likely network related error. '{error}' at '{file}:{line}'.",
|
||||
context: [
|
||||
'kind' => $e::class,
|
||||
'error' => $e->getMessage(),
|
||||
'line' => $e->getLine(),
|
||||
'file' => after($e->getFile(), ROOT_PATH),
|
||||
]
|
||||
)
|
||||
), code: 500, previous: $e
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -409,9 +409,31 @@ class PlexManage implements ManageInterface
|
||||
$backend = ag_set($backend, 'user', $map[$user]);
|
||||
$backend = ag_set($backend, 'options.plex_user_uuid', $uuid[$user]);
|
||||
|
||||
$question = new Question(
|
||||
r(
|
||||
<<<HELP
|
||||
<question>[<value>{name}</value>] User PIN</question>. {default}
|
||||
------------------
|
||||
<notice>Leave empty if the user doesn't have a PIN.</notice>
|
||||
------------------
|
||||
HELP. PHP_EOL . '> ',
|
||||
[
|
||||
'name' => ag($backend, 'name'),
|
||||
'default' => null !== $choice ? "<value>[Default: {$choice}]</value>" : ''
|
||||
]
|
||||
),
|
||||
ag($backend, 'options.' . Options::PLEX_USER_PIN)
|
||||
);
|
||||
|
||||
$pin = $this->questionHelper->ask($this->input, $this->output, $question);
|
||||
if (!empty($pin)) {
|
||||
$backend = ag_set($backend, 'options.' . Options::PLEX_USER_PIN, $pin);
|
||||
}
|
||||
|
||||
$this->output->writeln(
|
||||
r('<info>Requesting plex token for [{user}] from plex.tv API.</info>', [
|
||||
r("<info>Requesting plex token for '{user}'{pin} from plex.tv API.</info>", [
|
||||
'user' => ag($userInfo[$map[$user]], 'name') ?? 'None',
|
||||
'pin' => empty($pin) ? '' : ' with PIN'
|
||||
])
|
||||
);
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ final class Options
|
||||
public const string LIMIT_RESULTS = 'LIMIT_RESULTS';
|
||||
public const string NO_CHECK = 'NO_CHECK';
|
||||
public const string LOG_WRITER = 'LOG_WRITER';
|
||||
public const string PLEX_USER_PIN = 'PLEX_USER_PIN';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user