diff --git a/3rdparty/jwt/BeforeValidException.php b/3rdparty/jwt/BeforeValidException.php
index a6ee2f7c..c147852b 100644
--- a/3rdparty/jwt/BeforeValidException.php
+++ b/3rdparty/jwt/BeforeValidException.php
@@ -1,7 +1,7 @@
+ */
+class CachedKeySet implements ArrayAccess
+{
+ /**
+ * @var string
+ */
+ private $jwksUri;
+ /**
+ * @var ClientInterface
+ */
+ private $httpClient;
+ /**
+ * @var RequestFactoryInterface
+ */
+ private $httpFactory;
+ /**
+ * @var CacheItemPoolInterface
+ */
+ private $cache;
+ /**
+ * @var ?int
+ */
+ private $expiresAfter;
+ /**
+ * @var ?CacheItemInterface
+ */
+ private $cacheItem;
+ /**
+ * @var array>
+ */
+ private $keySet;
+ /**
+ * @var string
+ */
+ private $cacheKey;
+ /**
+ * @var string
+ */
+ private $cacheKeyPrefix = 'jwks';
+ /**
+ * @var int
+ */
+ private $maxKeyLength = 64;
+ /**
+ * @var bool
+ */
+ private $rateLimit;
+ /**
+ * @var string
+ */
+ private $rateLimitCacheKey;
+ /**
+ * @var int
+ */
+ private $maxCallsPerMinute = 10;
+ /**
+ * @var string|null
+ */
+ private $defaultAlg;
+
+ public function __construct(
+ string $jwksUri,
+ ClientInterface $httpClient,
+ RequestFactoryInterface $httpFactory,
+ CacheItemPoolInterface $cache,
+ int $expiresAfter = null,
+ bool $rateLimit = false,
+ string $defaultAlg = null
+ ) {
+ $this->jwksUri = $jwksUri;
+ $this->httpClient = $httpClient;
+ $this->httpFactory = $httpFactory;
+ $this->cache = $cache;
+ $this->expiresAfter = $expiresAfter;
+ $this->rateLimit = $rateLimit;
+ $this->defaultAlg = $defaultAlg;
+ $this->setCacheKeys();
+ }
+
+ /**
+ * @param string $keyId
+ * @return Key
+ */
+ public function offsetGet($keyId): Key
+ {
+ if (!$this->keyIdExists($keyId)) {
+ throw new OutOfBoundsException('Key ID not found');
+ }
+ return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg);
+ }
+
+ /**
+ * @param string $keyId
+ * @return bool
+ */
+ public function offsetExists($keyId): bool
+ {
+ return $this->keyIdExists($keyId);
+ }
+
+ /**
+ * @param string $offset
+ * @param Key $value
+ */
+ public function offsetSet($offset, $value): void
+ {
+ throw new LogicException('Method not implemented');
+ }
+
+ /**
+ * @param string $offset
+ */
+ public function offsetUnset($offset): void
+ {
+ throw new LogicException('Method not implemented');
+ }
+
+ /**
+ * @return array
+ */
+ private function formatJwksForCache(string $jwks): array
+ {
+ $jwks = json_decode($jwks, true);
+
+ if (!isset($jwks['keys'])) {
+ throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
+ }
+
+ if (empty($jwks['keys'])) {
+ throw new InvalidArgumentException('JWK Set did not contain any keys');
+ }
+
+ $keys = [];
+ foreach ($jwks['keys'] as $k => $v) {
+ $kid = isset($v['kid']) ? $v['kid'] : $k;
+ $keys[(string) $kid] = $v;
+ }
+
+ return $keys;
+ }
+
+ private function keyIdExists(string $keyId): bool
+ {
+ if (null === $this->keySet) {
+ $item = $this->getCacheItem();
+ // Try to load keys from cache
+ if ($item->isHit()) {
+ // item found! retrieve it
+ $this->keySet = $item->get();
+ // If the cached item is a string, the JWKS response was cached (previous behavior).
+ // Parse this into expected format array instead.
+ if (\is_string($this->keySet)) {
+ $this->keySet = $this->formatJwksForCache($this->keySet);
+ }
+ }
+ }
+
+ if (!isset($this->keySet[$keyId])) {
+ if ($this->rateLimitExceeded()) {
+ return false;
+ }
+ $request = $this->httpFactory->createRequest('GET', $this->jwksUri);
+ $jwksResponse = $this->httpClient->sendRequest($request);
+ $this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody());
+
+ if (!isset($this->keySet[$keyId])) {
+ return false;
+ }
+
+ $item = $this->getCacheItem();
+ $item->set($this->keySet);
+ if ($this->expiresAfter) {
+ $item->expiresAfter($this->expiresAfter);
+ }
+ $this->cache->save($item);
+ }
+
+ return true;
+ }
+
+ private function rateLimitExceeded(): bool
+ {
+ if (!$this->rateLimit) {
+ return false;
+ }
+
+ $cacheItem = $this->cache->getItem($this->rateLimitCacheKey);
+ if (!$cacheItem->isHit()) {
+ $cacheItem->expiresAfter(1); // # of calls are cached each minute
+ }
+
+ $callsPerMinute = (int) $cacheItem->get();
+ if (++$callsPerMinute > $this->maxCallsPerMinute) {
+ return true;
+ }
+ $cacheItem->set($callsPerMinute);
+ $this->cache->save($cacheItem);
+ return false;
+ }
+
+ private function getCacheItem(): CacheItemInterface
+ {
+ if (\is_null($this->cacheItem)) {
+ $this->cacheItem = $this->cache->getItem($this->cacheKey);
+ }
+
+ return $this->cacheItem;
+ }
+
+ private function setCacheKeys(): void
+ {
+ if (empty($this->jwksUri)) {
+ throw new RuntimeException('JWKS URI is empty');
+ }
+
+ // ensure we do not have illegal characters
+ $key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri);
+
+ // add prefix
+ $key = $this->cacheKeyPrefix . $key;
+
+ // Hash keys if they exceed $maxKeyLength of 64
+ if (\strlen($key) > $this->maxKeyLength) {
+ $key = substr(hash('sha256', $key), 0, $this->maxKeyLength);
+ }
+
+ $this->cacheKey = $key;
+
+ if ($this->rateLimit) {
+ // add prefix
+ $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key;
+
+ // Hash keys if they exceed $maxKeyLength of 64
+ if (\strlen($rateLimitKey) > $this->maxKeyLength) {
+ $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength);
+ }
+
+ $this->rateLimitCacheKey = $rateLimitKey;
+ }
+ }
+}
diff --git a/3rdparty/jwt/ExpiredException.php b/3rdparty/jwt/ExpiredException.php
index 3597370a..81ba52d4 100644
--- a/3rdparty/jwt/ExpiredException.php
+++ b/3rdparty/jwt/ExpiredException.php
@@ -1,7 +1,7 @@
+ * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
+ * @link https://github.com/firebase/php-jwt
+ */
+class JWK
+{
+ private const OID = '1.2.840.10045.2.1';
+ private const ASN1_OBJECT_IDENTIFIER = 0x06;
+ private const ASN1_SEQUENCE = 0x10; // also defined in JWT
+ private const ASN1_BIT_STRING = 0x03;
+ private const EC_CURVES = [
+ 'P-256' => '1.2.840.10045.3.1.7', // Len: 64
+ 'secp256k1' => '1.3.132.0.10', // Len: 64
+ // 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported)
+ // 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
+ ];
+
+ /**
+ * Parse a set of JWK keys
+ *
+ * @param array $jwks The JSON Web Key Set as an associative array
+ * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
+ * JSON Web Key Set
+ *
+ * @return array An associative array of key IDs (kid) to Key objects
+ *
+ * @throws InvalidArgumentException Provided JWK Set is empty
+ * @throws UnexpectedValueException Provided JWK Set was invalid
+ * @throws DomainException OpenSSL failure
+ *
+ * @uses parseKey
+ */
+ public static function parseKeySet(array $jwks, string $defaultAlg = null): array
+ {
+ $keys = [];
+
+ if (!isset($jwks['keys'])) {
+ throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
+ }
+
+ if (empty($jwks['keys'])) {
+ throw new InvalidArgumentException('JWK Set did not contain any keys');
+ }
+
+ foreach ($jwks['keys'] as $k => $v) {
+ $kid = isset($v['kid']) ? $v['kid'] : $k;
+ if ($key = self::parseKey($v, $defaultAlg)) {
+ $keys[(string) $kid] = $key;
+ }
+ }
+
+ if (0 === \count($keys)) {
+ throw new UnexpectedValueException('No supported algorithms found in JWK Set');
+ }
+
+ return $keys;
+ }
+
+ /**
+ * Parse a JWK key
+ *
+ * @param array $jwk An individual JWK
+ * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the
+ * JSON Web Key Set
+ *
+ * @return Key The key object for the JWK
+ *
+ * @throws InvalidArgumentException Provided JWK is empty
+ * @throws UnexpectedValueException Provided JWK was invalid
+ * @throws DomainException OpenSSL failure
+ *
+ * @uses createPemFromModulusAndExponent
+ */
+ public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
+ {
+ if (empty($jwk)) {
+ throw new InvalidArgumentException('JWK must not be empty');
+ }
+
+ if (!isset($jwk['kty'])) {
+ throw new UnexpectedValueException('JWK must contain a "kty" parameter');
+ }
+
+ if (!isset($jwk['alg'])) {
+ if (\is_null($defaultAlg)) {
+ // The "alg" parameter is optional in a KTY, but an algorithm is required
+ // for parsing in this library. Use the $defaultAlg parameter when parsing the
+ // key set in order to prevent this error.
+ // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
+ throw new UnexpectedValueException('JWK must contain an "alg" parameter');
+ }
+ $jwk['alg'] = $defaultAlg;
+ }
+
+ switch ($jwk['kty']) {
+ case 'RSA':
+ if (!empty($jwk['d'])) {
+ throw new UnexpectedValueException('RSA private keys are not supported');
+ }
+ if (!isset($jwk['n']) || !isset($jwk['e'])) {
+ throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"');
+ }
+
+ $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']);
+ $publicKey = \openssl_pkey_get_public($pem);
+ if (false === $publicKey) {
+ throw new DomainException(
+ 'OpenSSL error: ' . \openssl_error_string()
+ );
+ }
+ return new Key($publicKey, $jwk['alg']);
+ case 'EC':
+ if (isset($jwk['d'])) {
+ // The key is actually a private key
+ throw new UnexpectedValueException('Key data must be for a public key');
+ }
+
+ if (empty($jwk['crv'])) {
+ throw new UnexpectedValueException('crv not set');
+ }
+
+ if (!isset(self::EC_CURVES[$jwk['crv']])) {
+ throw new DomainException('Unrecognised or unsupported EC curve');
+ }
+
+ if (empty($jwk['x']) || empty($jwk['y'])) {
+ throw new UnexpectedValueException('x and y not set');
+ }
+
+ $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']);
+ return new Key($publicKey, $jwk['alg']);
+ default:
+ // Currently only RSA is supported
+ break;
+ }
+
+ return null;
+ }
+
+ /**
+ * Converts the EC JWK values to pem format.
+ *
+ * @param string $crv The EC curve (only P-256 is supported)
+ * @param string $x The EC x-coordinate
+ * @param string $y The EC y-coordinate
+ *
+ * @return string
+ */
+ private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string
+ {
+ $pem =
+ self::encodeDER(
+ self::ASN1_SEQUENCE,
+ self::encodeDER(
+ self::ASN1_SEQUENCE,
+ self::encodeDER(
+ self::ASN1_OBJECT_IDENTIFIER,
+ self::encodeOID(self::OID)
+ )
+ . self::encodeDER(
+ self::ASN1_OBJECT_IDENTIFIER,
+ self::encodeOID(self::EC_CURVES[$crv])
+ )
+ ) .
+ self::encodeDER(
+ self::ASN1_BIT_STRING,
+ \chr(0x00) . \chr(0x04)
+ . JWT::urlsafeB64Decode($x)
+ . JWT::urlsafeB64Decode($y)
+ )
+ );
+
+ return sprintf(
+ "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
+ wordwrap(base64_encode($pem), 64, "\n", true)
+ );
+ }
+
+ /**
+ * Create a public key represented in PEM format from RSA modulus and exponent information
+ *
+ * @param string $n The RSA modulus encoded in Base64
+ * @param string $e The RSA exponent encoded in Base64
+ *
+ * @return string The RSA public key represented in PEM format
+ *
+ * @uses encodeLength
+ */
+ private static function createPemFromModulusAndExponent(
+ string $n,
+ string $e
+ ): string {
+ $mod = JWT::urlsafeB64Decode($n);
+ $exp = JWT::urlsafeB64Decode($e);
+
+ $modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod);
+ $publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp);
+
+ $rsaPublicKey = \pack(
+ 'Ca*a*a*',
+ 48,
+ self::encodeLength(\strlen($modulus) + \strlen($publicExponent)),
+ $modulus,
+ $publicExponent
+ );
+
+ // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
+ $rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
+ $rsaPublicKey = \chr(0) . $rsaPublicKey;
+ $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey;
+
+ $rsaPublicKey = \pack(
+ 'Ca*a*',
+ 48,
+ self::encodeLength(\strlen($rsaOID . $rsaPublicKey)),
+ $rsaOID . $rsaPublicKey
+ );
+
+ return "-----BEGIN PUBLIC KEY-----\r\n" .
+ \chunk_split(\base64_encode($rsaPublicKey), 64) .
+ '-----END PUBLIC KEY-----';
+ }
+
+ /**
+ * DER-encode the length
+ *
+ * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
+ * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
+ *
+ * @param int $length
+ * @return string
+ */
+ private static function encodeLength(int $length): string
+ {
+ if ($length <= 0x7F) {
+ return \chr($length);
+ }
+
+ $temp = \ltrim(\pack('N', $length), \chr(0));
+
+ return \pack('Ca*', 0x80 | \strlen($temp), $temp);
+ }
+
+ /**
+ * Encodes a value into a DER object.
+ * Also defined in Firebase\JWT\JWT
+ *
+ * @param int $type DER tag
+ * @param string $value the value to encode
+ * @return string the encoded object
+ */
+ private static function encodeDER(int $type, string $value): string
+ {
+ $tag_header = 0;
+ if ($type === self::ASN1_SEQUENCE) {
+ $tag_header |= 0x20;
+ }
+
+ // Type
+ $der = \chr($tag_header | $type);
+
+ // Length
+ $der .= \chr(\strlen($value));
+
+ return $der . $value;
+ }
+
+ /**
+ * Encodes a string into a DER-encoded OID.
+ *
+ * @param string $oid the OID string
+ * @return string the binary DER-encoded OID
+ */
+ private static function encodeOID(string $oid): string
+ {
+ $octets = explode('.', $oid);
+
+ // Get the first octet
+ $first = (int) array_shift($octets);
+ $second = (int) array_shift($octets);
+ $oid = \chr($first * 40 + $second);
+
+ // Iterate over subsequent octets
+ foreach ($octets as $octet) {
+ if ($octet == 0) {
+ $oid .= \chr(0x00);
+ continue;
+ }
+ $bin = '';
+
+ while ($octet) {
+ $bin .= \chr(0x80 | ($octet & 0x7f));
+ $octet >>= 7;
+ }
+ $bin[0] = $bin[0] & \chr(0x7f);
+
+ // Convert to big endian if necessary
+ if (pack('V', 65534) == pack('L', 65534)) {
+ $oid .= strrev($bin);
+ } else {
+ $oid .= $bin;
+ }
+ }
+
+ return $oid;
+ }
+}
diff --git a/3rdparty/jwt/JWT.php b/3rdparty/jwt/JWT.php
index 6d30e941..269e8caf 100644
--- a/3rdparty/jwt/JWT.php
+++ b/3rdparty/jwt/JWT.php
@@ -1,14 +1,20 @@
array('hash_hmac', 'SHA256'),
- 'HS512' => array('hash_hmac', 'SHA512'),
- 'HS384' => array('hash_hmac', 'SHA384'),
- 'RS256' => array('openssl', 'SHA256'),
- );
+ /**
+ * @var array
+ */
+ public static $supported_algs = [
+ 'ES384' => ['openssl', 'SHA384'],
+ 'ES256' => ['openssl', 'SHA256'],
+ 'ES256K' => ['openssl', 'SHA256'],
+ 'HS256' => ['hash_hmac', 'SHA256'],
+ 'HS384' => ['hash_hmac', 'SHA384'],
+ 'HS512' => ['hash_hmac', 'SHA512'],
+ 'RS256' => ['openssl', 'SHA256'],
+ 'RS384' => ['openssl', 'SHA384'],
+ 'RS512' => ['openssl', 'SHA512'],
+ 'EdDSA' => ['sodium_crypto', 'EdDSA'],
+ ];
/**
* Decodes a JWT string into a PHP object.
*
- * @param string $jwt The JWT
- * @param string|array $key The key, or map of keys.
- * If the algorithm used is asymmetric, this is the public key
- * @param array $allowed_algs List of supported verification algorithms
- * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
+ * @param string $jwt The JWT
+ * @param Key|array $keyOrKeyArray The Key or associative array of key IDs (kid) to Key objects.
+ * If the algorithm used is asymmetric, this is the public key
+ * Each Key object contains an algorithm and matching key.
+ * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384',
+ * 'HS512', 'RS256', 'RS384', and 'RS512'
*
- * @return object The JWT's payload as a PHP object
+ * @return stdClass The JWT's payload as a PHP object
*
+ * @throws InvalidArgumentException Provided key/key-array was empty or malformed
+ * @throws DomainException Provided JWT is malformed
* @throws UnexpectedValueException Provided JWT was invalid
* @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
* @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
@@ -64,56 +88,64 @@ class JWT
* @uses jsonDecode
* @uses urlsafeB64Decode
*/
- public static function decode($jwt, $key, $allowed_algs = array())
- {
- $timestamp = is_null(static::$timestamp) ? time() : static::$timestamp;
+ public static function decode(
+ string $jwt,
+ $keyOrKeyArray
+ ): stdClass {
+ // Validate JWT
+ $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
- if (empty($key)) {
+ if (empty($keyOrKeyArray)) {
throw new InvalidArgumentException('Key may not be empty');
}
- if (!is_array($allowed_algs)) {
- throw new InvalidArgumentException('Algorithm not allowed');
- }
- $tks = explode('.', $jwt);
- if (count($tks) != 3) {
+ $tks = \explode('.', $jwt);
+ if (\count($tks) !== 3) {
throw new UnexpectedValueException('Wrong number of segments');
}
list($headb64, $bodyb64, $cryptob64) = $tks;
- if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
+ $headerRaw = static::urlsafeB64Decode($headb64);
+ if (null === ($header = static::jsonDecode($headerRaw))) {
throw new UnexpectedValueException('Invalid header encoding');
}
- if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
+ $payloadRaw = static::urlsafeB64Decode($bodyb64);
+ if (null === ($payload = static::jsonDecode($payloadRaw))) {
throw new UnexpectedValueException('Invalid claims encoding');
}
+ if (\is_array($payload)) {
+ // prevent PHP Fatal Error in edge-cases when payload is empty array
+ $payload = (object) $payload;
+ }
+ if (!$payload instanceof stdClass) {
+ throw new UnexpectedValueException('Payload must be a JSON object');
+ }
$sig = static::urlsafeB64Decode($cryptob64);
-
if (empty($header->alg)) {
throw new UnexpectedValueException('Empty algorithm');
}
if (empty(static::$supported_algs[$header->alg])) {
throw new UnexpectedValueException('Algorithm not supported');
}
- if (!in_array($header->alg, $allowed_algs)) {
- throw new UnexpectedValueException('Algorithm not allowed');
+
+ $key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null);
+
+ // Check the algorithm
+ if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) {
+ // See issue #351
+ throw new UnexpectedValueException('Incorrect key for this algorithm');
}
- if (is_array($key) || $key instanceof \ArrayAccess) {
- if (isset($header->kid)) {
- $key = $key[$header->kid];
- } else {
- throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
- }
+ if (\in_array($header->alg, ['ES256', 'ES256K', 'ES384'], true)) {
+ // OpenSSL expects an ASN.1 DER sequence for ES256/ES256K/ES384 signatures
+ $sig = self::signatureToDER($sig);
}
-
- // Check the signature
- if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
+ if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) {
throw new SignatureInvalidException('Signature verification failed');
}
- // Check if the nbf if it is defined. This is the time that the
+ // Check the nbf if it is defined. This is the time that the
// token can actually be used. If it's not yet that time, abort.
if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
- 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
+ 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf)
);
}
@@ -122,7 +154,7 @@ public static function decode($jwt, $key, $allowed_algs = array())
// correctly used the nbf claim).
if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
- 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)
+ 'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat)
);
}
@@ -135,116 +167,172 @@ public static function decode($jwt, $key, $allowed_algs = array())
}
/**
- * Converts and signs a PHP object or array into a JWT string.
+ * Converts and signs a PHP array into a JWT string.
*
- * @param object|array $payload PHP object or array
- * @param string $key The secret key.
- * If the algorithm used is asymmetric, this is the private key
- * @param string $alg The signing algorithm.
- * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
- * @param mixed $keyId
- * @param array $head An array with header elements to attach
+ * @param array $payload PHP array
+ * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
+ * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
+ * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
+ * @param string $keyId
+ * @param array $head An array with header elements to attach
*
* @return string A signed JWT
*
* @uses jsonEncode
* @uses urlsafeB64Encode
*/
- public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
- {
- $header = array('typ' => 'JWT', 'alg' => $alg);
+ public static function encode(
+ array $payload,
+ $key,
+ string $alg,
+ string $keyId = null,
+ array $head = null
+ ): string {
+ $header = ['typ' => 'JWT', 'alg' => $alg];
if ($keyId !== null) {
$header['kid'] = $keyId;
}
- if ( isset($head) && is_array($head) ) {
- $header = array_merge($head, $header);
+ if (isset($head) && \is_array($head)) {
+ $header = \array_merge($head, $header);
}
- $segments = array();
- $segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
- $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
- $signing_input = implode('.', $segments);
+ $segments = [];
+ $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header));
+ $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload));
+ $signing_input = \implode('.', $segments);
$signature = static::sign($signing_input, $key, $alg);
$segments[] = static::urlsafeB64Encode($signature);
- return implode('.', $segments);
+ return \implode('.', $segments);
}
/**
* Sign a string with a given key and algorithm.
*
- * @param string $msg The message to sign
- * @param string|resource $key The secret key
- * @param string $alg The signing algorithm.
- * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
+ * @param string $msg The message to sign
+ * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
+ * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
+ * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
*
* @return string An encrypted message
*
- * @throws DomainException Unsupported algorithm was specified
+ * @throws DomainException Unsupported algorithm or bad key was specified
*/
- public static function sign($msg, $key, $alg = 'HS256')
- {
+ public static function sign(
+ string $msg,
+ $key,
+ string $alg
+ ): string {
if (empty(static::$supported_algs[$alg])) {
throw new DomainException('Algorithm not supported');
}
list($function, $algorithm) = static::$supported_algs[$alg];
- switch($function) {
+ switch ($function) {
case 'hash_hmac':
- return hash_hmac($algorithm, $msg, $key, true);
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException('key must be a string when using hmac');
+ }
+ return \hash_hmac($algorithm, $msg, $key, true);
case 'openssl':
$signature = '';
- $success = openssl_sign($msg, $signature, $key, $algorithm);
+ $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line
if (!$success) {
- throw new DomainException("OpenSSL unable to sign data");
- } else {
- return $signature;
+ throw new DomainException('OpenSSL unable to sign data');
+ }
+ if ($alg === 'ES256' || $alg === 'ES256K') {
+ $signature = self::signatureFromDER($signature, 256);
+ } elseif ($alg === 'ES384') {
+ $signature = self::signatureFromDER($signature, 384);
+ }
+ return $signature;
+ case 'sodium_crypto':
+ if (!\function_exists('sodium_crypto_sign_detached')) {
+ throw new DomainException('libsodium is not available');
+ }
+ if (!\is_string($key)) {
+ throw new InvalidArgumentException('key must be a string when using EdDSA');
+ }
+ try {
+ // The last non-empty line is used as the key.
+ $lines = array_filter(explode("\n", $key));
+ $key = base64_decode((string) end($lines));
+ if (\strlen($key) === 0) {
+ throw new DomainException('Key cannot be empty string');
+ }
+ return sodium_crypto_sign_detached($msg, $key);
+ } catch (Exception $e) {
+ throw new DomainException($e->getMessage(), 0, $e);
}
}
+
+ throw new DomainException('Algorithm not supported');
}
/**
* Verify a signature with the message, key and method. Not all methods
* are symmetric, so we must have a separate verify and sign method.
*
- * @param string $msg The original message (header and body)
- * @param string $signature The original signature
- * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key
- * @param string $alg The algorithm
+ * @param string $msg The original message (header and body)
+ * @param string $signature The original signature
+ * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
+ * @param string $alg The algorithm
*
* @return bool
*
- * @throws DomainException Invalid Algorithm or OpenSSL failure
+ * @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure
*/
- private static function verify($msg, $signature, $key, $alg)
- {
+ private static function verify(
+ string $msg,
+ string $signature,
+ $keyMaterial,
+ string $alg
+ ): bool {
if (empty(static::$supported_algs[$alg])) {
throw new DomainException('Algorithm not supported');
}
list($function, $algorithm) = static::$supported_algs[$alg];
- switch($function) {
+ switch ($function) {
case 'openssl':
- $success = openssl_verify($msg, $signature, $key, $algorithm);
- if (!$success) {
- throw new DomainException("OpenSSL unable to verify data: " . openssl_error_string());
- } else {
- return $signature;
+ $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line
+ if ($success === 1) {
+ return true;
+ }
+ if ($success === 0) {
+ return false;
+ }
+ // returns 1 on success, 0 on failure, -1 on error.
+ throw new DomainException(
+ 'OpenSSL error: ' . \openssl_error_string()
+ );
+ case 'sodium_crypto':
+ if (!\function_exists('sodium_crypto_sign_verify_detached')) {
+ throw new DomainException('libsodium is not available');
+ }
+ if (!\is_string($keyMaterial)) {
+ throw new InvalidArgumentException('key must be a string when using EdDSA');
+ }
+ try {
+ // The last non-empty line is used as the key.
+ $lines = array_filter(explode("\n", $keyMaterial));
+ $key = base64_decode((string) end($lines));
+ if (\strlen($key) === 0) {
+ throw new DomainException('Key cannot be empty string');
+ }
+ if (\strlen($signature) === 0) {
+ throw new DomainException('Signature cannot be empty string');
+ }
+ return sodium_crypto_sign_verify_detached($signature, $msg, $key);
+ } catch (Exception $e) {
+ throw new DomainException($e->getMessage(), 0, $e);
}
case 'hash_hmac':
default:
- $hash = hash_hmac($algorithm, $msg, $key, true);
- if (function_exists('hash_equals')) {
- return hash_equals($signature, $hash);
+ if (!\is_string($keyMaterial)) {
+ throw new InvalidArgumentException('key must be a string when using hmac');
}
- $len = min(static::safeStrlen($signature), static::safeStrlen($hash));
-
- $status = 0;
- for ($i = 0; $i < $len; $i++) {
- $status |= (ord($signature[$i]) ^ ord($hash[$i]));
- }
- $status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash));
-
- return ($status === 0);
+ $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true);
+ return self::constantTimeEquals($hash, $signature);
}
}
@@ -253,30 +341,16 @@ private static function verify($msg, $signature, $key, $alg)
*
* @param string $input JSON string
*
- * @return object Object representation of JSON string
+ * @return mixed The decoded JSON string
*
* @throws DomainException Provided string was invalid JSON
*/
- public static function jsonDecode($input)
+ public static function jsonDecode(string $input)
{
- if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
- /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
- * to specify that large ints (like Steam Transaction IDs) should be treated as
- * strings, rather than the PHP default behaviour of converting them to floats.
- */
- $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
- } else {
- /** Not all servers will support that, however, so for older versions we must
- * manually detect large ints in the JSON string and quote them (thus converting
- *them to strings) before decoding, hence the preg_replace() call.
- */
- $max_int_length = strlen((string) PHP_INT_MAX) - 1;
- $json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
- $obj = json_decode($json_without_bigints);
- }
+ $obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
- if (function_exists('json_last_error') && $errno = json_last_error()) {
- static::handleJsonError($errno);
+ if ($errno = \json_last_error()) {
+ self::handleJsonError($errno);
} elseif ($obj === null && $input !== 'null') {
throw new DomainException('Null result with non-null input');
}
@@ -284,22 +358,30 @@ public static function jsonDecode($input)
}
/**
- * Encode a PHP object into a JSON string.
+ * Encode a PHP array into a JSON string.
*
- * @param object|array $input A PHP object or array
+ * @param array $input A PHP array
*
- * @return string JSON representation of the PHP object or array
+ * @return string JSON representation of the PHP array
*
* @throws DomainException Provided object could not be encoded to valid JSON
*/
- public static function jsonEncode($input)
+ public static function jsonEncode(array $input): string
{
- $json = json_encode($input);
- if (function_exists('json_last_error') && $errno = json_last_error()) {
- static::handleJsonError($errno);
+ if (PHP_VERSION_ID >= 50400) {
+ $json = \json_encode($input, \JSON_UNESCAPED_SLASHES);
+ } else {
+ // PHP 5.3 only
+ $json = \json_encode($input);
+ }
+ if ($errno = \json_last_error()) {
+ self::handleJsonError($errno);
} elseif ($json === 'null' && $input !== null) {
throw new DomainException('Null result with non-null input');
}
+ if ($json === false) {
+ throw new DomainException('Provided object could not be encoded to valid JSON');
+ }
return $json;
}
@@ -309,15 +391,17 @@ public static function jsonEncode($input)
* @param string $input A Base64 encoded string
*
* @return string A decoded string
+ *
+ * @throws InvalidArgumentException invalid base64 characters
*/
- public static function urlsafeB64Decode($input)
+ public static function urlsafeB64Decode(string $input): string
{
- $remainder = strlen($input) % 4;
+ $remainder = \strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
- $input .= str_repeat('=', $padlen);
+ $input .= \str_repeat('=', $padlen);
}
- return base64_decode(strtr($input, '-_', '+/'));
+ return \base64_decode(\strtr($input, '-_', '+/'));
}
/**
@@ -327,9 +411,65 @@ public static function urlsafeB64Decode($input)
*
* @return string The base64 encode of what you passed in
*/
- public static function urlsafeB64Encode($input)
+ public static function urlsafeB64Encode(string $input): string
+ {
+ return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
+ }
+
+
+ /**
+ * Determine if an algorithm has been provided for each Key
+ *
+ * @param Key|ArrayAccess|array $keyOrKeyArray
+ * @param string|null $kid
+ *
+ * @throws UnexpectedValueException
+ *
+ * @return Key
+ */
+ private static function getKey(
+ $keyOrKeyArray,
+ ?string $kid
+ ): Key {
+ if ($keyOrKeyArray instanceof Key) {
+ return $keyOrKeyArray;
+ }
+
+ if (empty($kid)) {
+ throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
+ }
+
+ if ($keyOrKeyArray instanceof CachedKeySet) {
+ // Skip "isset" check, as this will automatically refresh if not set
+ return $keyOrKeyArray[$kid];
+ }
+
+ if (!isset($keyOrKeyArray[$kid])) {
+ throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
+ }
+
+ return $keyOrKeyArray[$kid];
+ }
+
+ /**
+ * @param string $left The string of known length to compare against
+ * @param string $right The user-supplied string
+ * @return bool
+ */
+ public static function constantTimeEquals(string $left, string $right): bool
{
- return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
+ if (\function_exists('hash_equals')) {
+ return \hash_equals($left, $right);
+ }
+ $len = \min(self::safeStrlen($left), self::safeStrlen($right));
+
+ $status = 0;
+ for ($i = 0; $i < $len; $i++) {
+ $status |= (\ord($left[$i]) ^ \ord($right[$i]));
+ }
+ $status |= (self::safeStrlen($left) ^ self::safeStrlen($right));
+
+ return ($status === 0);
}
/**
@@ -337,15 +477,19 @@ public static function urlsafeB64Encode($input)
*
* @param int $errno An error number from json_last_error()
*
+ * @throws DomainException
+ *
* @return void
*/
- private static function handleJsonError($errno)
+ private static function handleJsonError(int $errno): void
{
- $messages = array(
+ $messages = [
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
+ JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
- JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON'
- );
+ JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
+ JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
+ ];
throw new DomainException(
isset($messages[$errno])
? $messages[$errno]
@@ -356,15 +500,139 @@ private static function handleJsonError($errno)
/**
* Get the number of bytes in cryptographic strings.
*
- * @param string
+ * @param string $str
*
* @return int
*/
- private static function safeStrlen($str)
+ private static function safeStrlen(string $str): int
+ {
+ if (\function_exists('mb_strlen')) {
+ return \mb_strlen($str, '8bit');
+ }
+ return \strlen($str);
+ }
+
+ /**
+ * Convert an ECDSA signature to an ASN.1 DER sequence
+ *
+ * @param string $sig The ECDSA signature to convert
+ * @return string The encoded DER object
+ */
+ private static function signatureToDER(string $sig): string
+ {
+ // Separate the signature into r-value and s-value
+ $length = max(1, (int) (\strlen($sig) / 2));
+ list($r, $s) = \str_split($sig, $length);
+
+ // Trim leading zeros
+ $r = \ltrim($r, "\x00");
+ $s = \ltrim($s, "\x00");
+
+ // Convert r-value and s-value from unsigned big-endian integers to
+ // signed two's complement
+ if (\ord($r[0]) > 0x7f) {
+ $r = "\x00" . $r;
+ }
+ if (\ord($s[0]) > 0x7f) {
+ $s = "\x00" . $s;
+ }
+
+ return self::encodeDER(
+ self::ASN1_SEQUENCE,
+ self::encodeDER(self::ASN1_INTEGER, $r) .
+ self::encodeDER(self::ASN1_INTEGER, $s)
+ );
+ }
+
+ /**
+ * Encodes a value into a DER object.
+ *
+ * @param int $type DER tag
+ * @param string $value the value to encode
+ *
+ * @return string the encoded object
+ */
+ private static function encodeDER(int $type, string $value): string
+ {
+ $tag_header = 0;
+ if ($type === self::ASN1_SEQUENCE) {
+ $tag_header |= 0x20;
+ }
+
+ // Type
+ $der = \chr($tag_header | $type);
+
+ // Length
+ $der .= \chr(\strlen($value));
+
+ return $der . $value;
+ }
+
+ /**
+ * Encodes signature from a DER object.
+ *
+ * @param string $der binary signature in DER format
+ * @param int $keySize the number of bits in the key
+ *
+ * @return string the signature
+ */
+ private static function signatureFromDER(string $der, int $keySize): string
+ {
+ // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
+ list($offset, $_) = self::readDER($der);
+ list($offset, $r) = self::readDER($der, $offset);
+ list($offset, $s) = self::readDER($der, $offset);
+
+ // Convert r-value and s-value from signed two's compliment to unsigned
+ // big-endian integers
+ $r = \ltrim($r, "\x00");
+ $s = \ltrim($s, "\x00");
+
+ // Pad out r and s so that they are $keySize bits long
+ $r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT);
+ $s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT);
+
+ return $r . $s;
+ }
+
+ /**
+ * Reads binary DER-encoded data and decodes into a single object
+ *
+ * @param string $der the binary data in DER format
+ * @param int $offset the offset of the data stream containing the object
+ * to decode
+ *
+ * @return array{int, string|null} the new offset and the decoded object
+ */
+ private static function readDER(string $der, int $offset = 0): array
{
- if (function_exists('mb_strlen')) {
- return mb_strlen($str, '8bit');
+ $pos = $offset;
+ $size = \strlen($der);
+ $constructed = (\ord($der[$pos]) >> 5) & 0x01;
+ $type = \ord($der[$pos++]) & 0x1f;
+
+ // Length
+ $len = \ord($der[$pos++]);
+ if ($len & 0x80) {
+ $n = $len & 0x1f;
+ $len = 0;
+ while ($n-- && $pos < $size) {
+ $len = ($len << 8) | \ord($der[$pos++]);
+ }
}
- return strlen($str);
+
+ // Value
+ if ($type === self::ASN1_BIT_STRING) {
+ $pos++; // Skip the first contents octet (padding indicator)
+ $data = \substr($der, $pos, $len - 1);
+ $pos += $len - 1;
+ } elseif (!$constructed) {
+ $data = \substr($der, $pos, $len);
+ $pos += $len;
+ } else {
+ $data = null;
+ }
+
+ return [$pos, $data];
}
}
diff --git a/3rdparty/jwt/Key.php b/3rdparty/jwt/Key.php
new file mode 100644
index 00000000..00cf7f2e
--- /dev/null
+++ b/3rdparty/jwt/Key.php
@@ -0,0 +1,64 @@
+keyMaterial = $keyMaterial;
+ $this->algorithm = $algorithm;
+ }
+
+ /**
+ * Return the algorithm valid for this key
+ *
+ * @return string
+ */
+ public function getAlgorithm(): string
+ {
+ return $this->algorithm;
+ }
+
+ /**
+ * @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate
+ */
+ public function getKeyMaterial()
+ {
+ return $this->keyMaterial;
+ }
+}
diff --git a/3rdparty/jwt/LICENSE b/3rdparty/jwt/LICENSE
index cb0c49b3..11c01466 100644
--- a/3rdparty/jwt/LICENSE
+++ b/3rdparty/jwt/LICENSE
@@ -13,7 +13,7 @@ modification, are permitted provided that the following conditions are met:
disclaimer in the documentation and/or other materials provided
with the distribution.
- * Neither the name of Neuman Vong nor the names of other
+ * Neither the name of the copyright holder nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
diff --git a/3rdparty/jwt/SignatureInvalidException.php b/3rdparty/jwt/SignatureInvalidException.php
index 27332b21..d35dee9f 100644
--- a/3rdparty/jwt/SignatureInvalidException.php
+++ b/3rdparty/jwt/SignatureInvalidException.php
@@ -1,7 +1,7 @@
array (
+ 'editors_check_interval' => 3624
+ )
+ ```
+ To disable this check running, enter 0 value.
## ONLYOFFICE Docs editions
@@ -263,4 +280,4 @@ The table below will help you to make the right choice.
| Saving as PDF | + | + |
| | [Get it now](https://www.onlyoffice.com/download-docs.aspx?utm_source=github&utm_medium=cpc&utm_campaign=GitHubOwncloud#docs-community) | [Start Free Trial](https://www.onlyoffice.com/download-docs.aspx?utm_source=github&utm_medium=cpc&utm_campaign=GitHubOwncloud#docs-enterprise) |
-\* If supported by DMS.
\ No newline at end of file
+\* If supported by DMS.
diff --git a/appinfo/application.php b/appinfo/application.php
index ddf8afe3..3e0b9eaf 100644
--- a/appinfo/application.php
+++ b/appinfo/application.php
@@ -20,6 +20,7 @@
namespace OCA\Onlyoffice\AppInfo;
use OCP\AppFramework\App;
+use OCP\BackgroundJob\IJobList;
use OCP\Files\IMimeTypeDetector;
use OCP\Util;
use OCP\IPreview;
@@ -29,6 +30,7 @@
use OCA\Onlyoffice\Controller\CallbackController;
use OCA\Onlyoffice\Controller\EditorApiController;
use OCA\Onlyoffice\Controller\EditorController;
+use OCA\Onlyoffice\Controller\JobListController;
use OCA\Onlyoffice\Controller\SettingsApiController;
use OCA\Onlyoffice\Controller\SettingsController;
use OCA\Onlyoffice\Controller\TemplateController;
@@ -89,7 +91,10 @@ function () {
require_once __DIR__ . "/../3rdparty/jwt/BeforeValidException.php";
require_once __DIR__ . "/../3rdparty/jwt/ExpiredException.php";
require_once __DIR__ . "/../3rdparty/jwt/SignatureInvalidException.php";
+ require_once __DIR__ . "/../3rdparty/jwt/CachedKeySet.php";
require_once __DIR__ . "/../3rdparty/jwt/JWT.php";
+ require_once __DIR__ . "/../3rdparty/jwt/JWK.php";
+ require_once __DIR__ . "/../3rdparty/jwt/Key.php";
// Set the leeway for the JWT library in case the system clock is a second off
\Firebase\JWT\JWT::$leeway = $this->appConfig->GetJwtLeeway();
@@ -237,6 +242,14 @@ function () {
);
});
+ $checkBackgroundJobs = new JobListController(
+ $container->query("AppName"),
+ $container->query("Request"),
+ $container->query("Logger"),
+ $this->appConfig,
+ $container->query(IJobList::class)
+ );
+ $checkBackgroundJobs->checkAllJobs();
Hooks::connectHooks();
}
diff --git a/appinfo/info.xml b/appinfo/info.xml
index a9bc34dd..4b0c4cd1 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -6,7 +6,7 @@
ONLYOFFICE connector allows you to view, edit and collaborate on text documents, spreadsheets and presentations within ownCloud using ONLYOFFICE Docs. This will create a new Edit in ONLYOFFICE action within the document library for Office documents. This allows multiple users to co-author documents in real time from the familiar web interface and save the changes back to your file storage.
apl2
Ascensio System SIA
- 7.8.1
+ 8.2.1
Onlyoffice
@@ -31,4 +31,7 @@
OCA\Onlyoffice\Command\DocumentServer
+
+ OCA\Onlyoffice\Cron\EditorsCheck
+
\ No newline at end of file
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 4698484f..1f9b0199 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -28,6 +28,7 @@
["name" => "editor#public_page", "url" => "/s/{shareToken}", "verb" => "GET"],
["name" => "editor#users", "url" => "/ajax/users", "verb" => "GET"],
["name" => "editor#mention", "url" => "/ajax/mention", "verb" => "POST"],
+ ["name" => "editor#reference", "url" => "/ajax/reference", "verb" => "POST"],
["name" => "editor#create", "url" => "/ajax/new", "verb" => "POST"],
["name" => "editor#convert", "url" => "/ajax/convert", "verb" => "POST"],
["name" => "editor#save", "url" => "/ajax/save", "verb" => "POST"],
diff --git a/assets b/assets
index 4b96e283..f00ab3a3 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 4b96e283924e0481299b6400520829649634c23e
+Subproject commit f00ab3a3efe6e2f8542ba026d1fc1d72df7dfd5f
diff --git a/controller/callbackcontroller.php b/controller/callbackcontroller.php
index 9d268c1c..05704c9f 100644
--- a/controller/callbackcontroller.php
+++ b/controller/callbackcontroller.php
@@ -206,7 +206,7 @@ public function download($doc) {
$header = substr($header, strlen("Bearer "));
try {
- $decodedHeader = \Firebase\JWT\JWT::decode($header, $this->config->GetDocumentServerSecret(), array("HS256"));
+ $decodedHeader = \Firebase\JWT\JWT::decode($header, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256"));
} catch (\UnexpectedValueException $e) {
$this->logger->logException($e, ["message" => "Download with invalid jwt", "app" => $this->appName]);
return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN);
@@ -343,7 +343,7 @@ public function emptyfile($doc) {
$header = substr($header, strlen("Bearer "));
try {
- $decodedHeader = \Firebase\JWT\JWT::decode($header, $this->config->GetDocumentServerSecret(), array("HS256"));
+ $decodedHeader = \Firebase\JWT\JWT::decode($header, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256"));
} catch (\UnexpectedValueException $e) {
$this->logger->logException($e, ["message" => "Download empty with invalid jwt", "app" => $this->appName]);
return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN);
@@ -380,6 +380,7 @@ public function emptyfile($doc) {
* @param string $changesurl - link to file changes
* @param integer $forcesavetype - the type of force save action
* @param array $actions - the array of action
+ * @param string $filetype - extension of the document that is downloaded from the link specified with the url parameter
*
* @return array
*
@@ -388,7 +389,7 @@ public function emptyfile($doc) {
* @PublicPage
* @CORS
*/
- public function track($doc, $users, $key, $status, $url, $token, $history, $changesurl, $forcesavetype, $actions) {
+ public function track($doc, $users, $key, $status, $url, $token, $history, $changesurl, $forcesavetype, $actions, $filetype) {
list ($hashData, $error) = $this->crypt->ReadHash($doc);
if ($hashData === null) {
@@ -406,7 +407,7 @@ public function track($doc, $users, $key, $status, $url, $token, $history, $chan
if (!empty($this->config->GetDocumentServerSecret())) {
if (!empty($token)) {
try {
- $payload = \Firebase\JWT\JWT::decode($token, $this->config->GetDocumentServerSecret(), array("HS256"));
+ $payload = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256"));
} catch (\UnexpectedValueException $e) {
$this->logger->logException($e, ["message" => "Track with invalid jwt in body", "app" => $this->appName]);
return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN);
@@ -421,7 +422,7 @@ public function track($doc, $users, $key, $status, $url, $token, $history, $chan
$header = substr($header, strlen("Bearer "));
try {
- $decodedHeader = \Firebase\JWT\JWT::decode($header, $this->config->GetDocumentServerSecret(), array("HS256"));
+ $decodedHeader = \Firebase\JWT\JWT::decode($header, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256"));
$payload = $decodedHeader->payload;
} catch (\UnexpectedValueException $e) {
@@ -514,7 +515,7 @@ public function track($doc, $users, $key, $status, $url, $token, $history, $chan
$prevVersion = $file->getFileInfo()->getMtime();
$fileName = $file->getName();
$curExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
- $downloadExt = strtolower(pathinfo($url, PATHINFO_EXTENSION));
+ $downloadExt = $filetype;
$documentService = new DocumentService($this->trans, $this->config);
if ($downloadExt !== $curExt) {
diff --git a/controller/editorapicontroller.php b/controller/editorapicontroller.php
index 0f35dd20..c836127c 100644
--- a/controller/editorapicontroller.php
+++ b/controller/editorapicontroller.php
@@ -301,6 +301,10 @@ public function config($fileId, $filePath = null, $shareToken = null, $version =
"permissions" => [],
"title" => $fileName,
"url" => $fileUrl,
+ "referenceData" => [
+ "fileKey" => $file->getId(),
+ "instanceId" => $this->config->GetSystemValue("instanceid", true),
+ ],
],
"documentType" => $format["type"],
"editorConfig" => [
@@ -392,10 +396,15 @@ public function config($fileId, $filePath = null, $shareToken = null, $version =
}
$params["document"]["permissions"]["protect"] = $canProtect;
+ if (isset($shareToken)) {
+ $params["document"]["permissions"]["chat"] = false;
+ $params["document"]["permissions"]["protect"] = false;
+ }
+
$hashCallback = $this->crypt->GetHash(["userId" => $userId, "ownerId" => $ownerId, "fileId" => $file->getId(), "filePath" => $filePath, "shareToken" => $shareToken, "action" => "track"]);
$callback = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.track", ["doc" => $hashCallback]);
- if (!empty($this->config->GetStorageUrl())) {
+ if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) {
$callback = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $callback);
}
@@ -538,7 +547,7 @@ public function config($fileId, $filePath = null, $shareToken = null, $version =
}
if (!empty($this->config->GetDocumentServerSecret())) {
- $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret());
+ $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret(), "HS256");
$params["token"] = $token;
}
@@ -635,7 +644,7 @@ private function getUrl($file, $user = null, $shareToken = null, $version = 0, $
$fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.download", ["doc" => $hashUrl]);
- if (!empty($this->config->GetStorageUrl())) {
+ if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) {
$fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl);
}
@@ -764,4 +773,4 @@ private function isFavorite($fileId) {
return false;
}
-}
\ No newline at end of file
+}
diff --git a/controller/editorcontroller.php b/controller/editorcontroller.php
index e97fab26..b21e6c92 100644
--- a/controller/editorcontroller.php
+++ b/controller/editorcontroller.php
@@ -264,7 +264,7 @@ public function create($name, $dir, $templateId = null, $targetPath = null, $sha
$targetName = $targetFile->getName();
$targetExt = strtolower(pathinfo($targetName, PATHINFO_EXTENSION));
$targetKey = $this->fileUtility->getKey($targetFile);
-
+
$fileUrl = $this->getUrl($targetFile, $user, $shareToken);
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
@@ -311,7 +311,7 @@ public function create($name, $dir, $templateId = null, $targetPath = null, $sha
* @param string $name - file name
* @param string $dir - folder path
* @param string $templateId - file identifier
- *
+ *
* @return TemplateResponse|RedirectResponse
*
* @NoAdminRequired
@@ -438,7 +438,7 @@ public function mention($fileId, $anchor, $comment, $emails) {
foreach ($emails as $email) {
$recipients = $this->userManager->getByEmail($email);
foreach ($recipients as $recipient) {
- $recipientId = $recipient->getUID();
+ $recipientId = $recipient->getUID();
if (!in_array($recipientId, $recipientIds)) {
array_push($recipientIds, $recipientId);
}
@@ -537,6 +537,75 @@ public function mention($fileId, $anchor, $comment, $emails) {
return ["message" => $this->trans->t("Notification sent successfully")];
}
+ /**
+ * Reference data
+ *
+ * @param array $referenceData - reference data
+ * @param string $path - file path
+ *
+ * @return array
+ *
+ * @NoAdminRequired
+ * @PublicPage
+ */
+ public function reference($referenceData, $path = null) {
+ $this->logger->debug("reference: " . json_encode($referenceData) . " $path", ["app" => $this->appName]);
+
+ if (!$this->config->isUserAllowedToUse()) {
+ return ["error" => $this->trans->t("Not permitted")];
+ }
+
+ $user = $this->userSession->getUser();
+ if (empty($user)) {
+ return ["error" => $this->trans->t("Not permitted")];
+ }
+
+ $userId = $user->getUID();
+
+ $file = null;
+ $fileId = (integer)($referenceData["fileKey"] ?? 0);
+ if (!empty($fileId)
+ && $referenceData["instanceId"] === $this->config->GetSystemValue("instanceid", true)) {
+ list ($file, $error, $share) = $this->getFile($userId, $fileId);
+ }
+
+ $userFolder = $this->root->getUserFolder($userId);
+ if ($file === null
+ && $path !== null
+ && $userFolder->nodeExists($path)) {
+ $node = $userFolder->get($path);
+ if ($node instanceof File
+ && $node->isReadable()) {
+ $file = $node;
+ }
+ }
+
+ if ($file === null) {
+ $this->logger->error("Reference not found: $fileId $path", ["app" => $this->appName]);
+ return ["error" => $this->trans->t("File not found")];
+ }
+
+ $fileName = $file->getName();
+ $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
+
+ $response = [
+ "fileType" => $ext,
+ "path" => $userFolder->getRelativePath($file->getPath()),
+ "referenceData" => [
+ "fileKey" => $file->getId(),
+ "instanceId" => $this->config->GetSystemValue("instanceid", true),
+ ],
+ "url" => $this->getUrl($file, $user),
+ ];
+
+ if (!empty($this->config->GetDocumentServerSecret())) {
+ $token = \Firebase\JWT\JWT::encode($response, $this->config->GetDocumentServerSecret(), "HS256");
+ $response["token"] = $token;
+ }
+
+ return $response;
+ }
+
/**
* Conversion file to Office Open XML format
*
@@ -890,8 +959,11 @@ public function version($fileId, $version) {
$fileUrl = $this->getUrl($file, $user, null, $version);
}
$key = DocumentService::GenerateRevisionId($key);
+ $fileName = $file->getName();
+ $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$result = [
+ "fileType" => $ext,
"url" => $fileUrl,
"version" => $version,
"key" => $key
@@ -911,13 +983,14 @@ public function version($fileId, $version) {
$prevVersionUrl = $this->getUrl($file, $user, null, $version - 1);
$result["previous"] = [
+ "fileType" => $ext,
"key" => $prevVersionKey,
"url" => $prevVersionUrl
];
}
if (!empty($this->config->GetDocumentServerSecret())) {
- $token = \Firebase\JWT\JWT::encode($result, $this->config->GetDocumentServerSecret());
+ $token = \Firebase\JWT\JWT::encode($result, $this->config->GetDocumentServerSecret(), "HS256");
$result["token"] = $token;
}
@@ -1019,7 +1092,7 @@ public function url($filePath) {
];
if (!empty($this->config->GetDocumentServerSecret())) {
- $token = \Firebase\JWT\JWT::encode($result, $this->config->GetDocumentServerSecret());
+ $token = \Firebase\JWT\JWT::encode($result, $this->config->GetDocumentServerSecret(), "HS256");
$result["token"] = $token;
}
@@ -1293,7 +1366,7 @@ private function getUrl($file, $user = null, $shareToken = null, $version = 0, $
$fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.download", ["doc" => $hashUrl]);
- if (!empty($this->config->GetStorageUrl())) {
+ if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) {
$fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl);
}
diff --git a/controller/joblistcontroller.php b/controller/joblistcontroller.php
new file mode 100644
index 00000000..c2722692
--- /dev/null
+++ b/controller/joblistcontroller.php
@@ -0,0 +1,123 @@
+logger = $logger;
+ $this->config = $config;
+ $this->jobList = $jobList;
+ }
+
+ /**
+ * Add a job to list
+ *
+ * @param IJob|string $job
+ */
+ private function addJob($job) {
+ if (!$this->jobList->has($job, null)) {
+ $this->jobList->add($job);
+ $this->logger->debug("Job '".$job."' added to JobList.", ["app" => $this->appName]);
+ }
+ }
+
+ /**
+ * Remove a job from list
+ *
+ * @param IJob|string $job
+ */
+ private function removeJob($job) {
+ if ($this->jobList->has($job, null)) {
+ $this->jobList->remove($job);
+ $this->logger->debug("Job '".$job."' removed from JobList.", ["app" => $this->appName]);
+ }
+ }
+
+ /**
+ * Add or remove EditorsCheck job depending on the value of _editors_check_interval
+ *
+ */
+ private function checkEditorsCheckJob() {
+ if ($this->config->GetEditorsCheckInterval() > 0) {
+ $this->addJob(EditorsCheck::class);
+ } else {
+ $this->removeJob(EditorsCheck::class);
+ }
+ }
+
+ /**
+ * Method for sequentially calling checks of all jobs
+ *
+ */
+ public function checkAllJobs() {
+ $this->checkEditorsCheckJob();
+ }
+}
diff --git a/controller/settingscontroller.php b/controller/settingscontroller.php
index 43b0de5a..5e112e51 100644
--- a/controller/settingscontroller.php
+++ b/controller/settingscontroller.php
@@ -110,6 +110,7 @@ public function index() {
"storageUrl" => $this->config->GetStorageUrl(),
"verifyPeerOff" => $this->config->GetVerifyPeerOff(),
"secret" => $this->config->GetDocumentServerSecret(true),
+ "jwtHeader" => $this->config->JwtHeader(true),
"demo" => $this->config->GetDemoData(),
"currentServer" => $this->urlGenerator->getAbsoluteURL("/"),
"formats" => $this->config->FormatsSetting(),
@@ -130,7 +131,8 @@ public function index() {
"macros" => $this->config->GetCustomizationMacros(),
"reviewDisplay" => $this->config->GetCustomizationReviewDisplay(),
"theme" => $this->config->GetCustomizationTheme(),
- "templates" => $this->GetGlobalTemplates()
+ "templates" => $this->GetGlobalTemplates(),
+ "linkToDocs" => $this->config->GetLinkToDocs()
];
return new TemplateResponse($this->appName, "settings", $data, "blank");
}
@@ -138,6 +140,7 @@ public function index() {
/**
* Save address settings
*
+ * @param string $jwtHeader - jwt header
* @param string $documentserver - document service address
* @param string $documentserverInternal - document service address available from ownCloud
* @param string $storageUrl - ownCloud address available from document server
@@ -152,6 +155,7 @@ public function SaveAddress($documentserver,
$storageUrl,
$verifyPeerOff,
$secret,
+ $jwtHeader,
$demo
) {
$error = null;
@@ -163,6 +167,7 @@ public function SaveAddress($documentserver,
$this->config->SetVerifyPeerOff($verifyPeerOff);
$this->config->SetDocumentServerInternalUrl($documentserverInternal);
$this->config->SetDocumentServerSecret($secret);
+ $this->config->SetJwtHeader($jwtHeader);
}
$this->config->SetStorageUrl($storageUrl);
@@ -186,6 +191,7 @@ public function SaveAddress($documentserver,
"documentserverInternal" => $this->config->GetDocumentServerInternalUrl(true),
"storageUrl" => $this->config->GetStorageUrl(),
"secret" => $this->config->GetDocumentServerSecret(true),
+ "jwtHeader" => $this->config->JwtHeader(true),
"error" => $error,
"version" => $version,
];
diff --git a/controller/webassetcontroller.php b/controller/webassetcontroller.php
index 4232f128..200db7c4 100644
--- a/controller/webassetcontroller.php
+++ b/controller/webassetcontroller.php
@@ -19,7 +19,6 @@
namespace OCA\Onlyoffice\Controller;
-use GuzzleHttp\Mimetypes;
use OC\AppFramework\Http;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataDisplayResponse;
@@ -65,7 +64,7 @@ public function get(): Response {
$filePath = \realpath( $basePath . '/js/web/onlyoffice.js');
try {
return new DataDisplayResponse(\file_get_contents($filePath), Http::STATUS_OK, [
- 'Content-Type' => $this->getMimeType($filePath),
+ 'Content-Type' => "text/javascript",
'Content-Length' => \filesize($filePath),
'Cache-Control' => 'max-age=0, no-cache, no-store, must-revalidate',
'Pragma' => 'no-cache',
@@ -77,9 +76,4 @@ public function get(): Response {
return new DataResponse(["message" => $e->getMessage()], Http::STATUS_NOT_FOUND);
}
}
-
- private function getMimeType(string $filename): string {
- $mimeTypes = Mimetypes::getInstance();
- return $mimeTypes->fromFilename($filename);
- }
}
diff --git a/css/settings.css b/css/settings.css
index bf5c3d3d..0899c3ba 100644
--- a/css/settings.css
+++ b/css/settings.css
@@ -70,3 +70,94 @@
.onlyoffice-option-desc {
margin-left: 20px;
}
+#onlyofficeDocsCloudBannerWrapper {
+ max-width: 596px;
+ min-height: 108px;
+ text-align: center;
+ background-image: url("../img/get-editors-background.svg");
+ background-repeat: no-repeat;
+ background-color: #FFFFFF;
+ border: 1px solid #EFEFEF;
+ border-radius: 3px;
+ display: flex;
+ flex-direction: row;
+ margin-top: 10px;
+}
+
+#onlyofficeDocsCloudBannerPicWrapper {
+ height: 100%;
+ width: 20%;
+ padding: 10px 0 0 20px;
+}
+
+#onlyofficeDocsCloudBannerPic {
+ content: url("../img/get-editors-pic.svg");
+}
+
+#onlyofficeDocsCloudBannerContent {
+ height: 100%;
+ width: 80%;
+ color: #000000;
+ display: flex;
+ flex-direction: row;
+ margin: auto;
+}
+
+#onlyofficeDocsCloudBannerContentText {
+ width: 60%;
+ height: 100%;
+ color: #000;
+ font-size: 14px;
+ text-align: justify;
+ padding-left: 15px;
+}
+
+#onlyofficeDocsCloudBannerContentText h2 {
+ margin: 0;
+ color: #000000;
+}
+
+#onlyofficeDocsCloudBannerContentButtonWrapper {
+ width: 40%;
+ height: 100%;
+ margin: auto;
+}
+
+#onlyofficeDocsCloudBannerContentButton {
+ background-color: #EDEDED;
+ color: #000;
+ border: 1px solid #DBDBDB;
+}
+
+@media (max-width: 580px) {
+ #onlyofficeDocsCloudBannerPicWrapper {
+ padding: 20px 0 0 5px;
+ width: 30%;
+ }
+ #onlyofficeDocsCloudBannerContent {
+ flex-direction: column;
+ width: 70%;
+ }
+ #onlyofficeDocsCloudBannerContentText {
+ width: 100%;
+ padding-right: 5px;
+ }
+ #onlyofficeDocsCloudBannerContentButtonWrapper {
+ width: 100%;
+ padding: 15px 0 15px 15px;
+ text-align: left;
+ }
+}
+
+@media (max-width: 335px) {
+ #onlyofficeDocsCloudBannerWrapper {
+ flex-direction: column;
+ }
+ #onlyofficeDocsCloudBannerPicWrapper {
+ width: 100%;
+ padding: unset;
+ }
+ #onlyofficeDocsCloudBannerContent {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/img/get-editors-background.svg b/img/get-editors-background.svg
new file mode 100644
index 00000000..ff6f91dc
--- /dev/null
+++ b/img/get-editors-background.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/img/get-editors-pic.svg b/img/get-editors-pic.svg
new file mode 100644
index 00000000..939d3d29
--- /dev/null
+++ b/img/get-editors-pic.svg
@@ -0,0 +1,57 @@
+
\ No newline at end of file
diff --git a/js/editor.js b/js/editor.js
index 49975165..a925aafb 100644
--- a/js/editor.js
+++ b/js/editor.js
@@ -35,9 +35,12 @@
OCA.Onlyoffice.template = $("#iframeEditor").data("template");
OCA.Onlyoffice.inframe = !!$("#iframeEditor").data("inframe");
OCA.Onlyoffice.anchor = $("#iframeEditor").attr("data-anchor");
+ OCA.Onlyoffice.currentWindow = window;
if (OCA.Onlyoffice.inframe) {
OCA.Onlyoffice.faviconBase = $('link[rel="icon"]').attr("href");
+ OCA.Onlyoffice.currentWindow = window.parent;
+ OCA.Onlyoffice.titleBase = OCA.Onlyoffice.currentWindow.document.title;
}
if (!OCA.Onlyoffice.fileId && !OCA.Onlyoffice.shareToken) {
@@ -111,7 +114,7 @@
if (docIsChanged !== event.data) {
var titleChange = function () {
- window.document.title = config.document.title + (event.data ? " *" : "") + " - " + oc_defaults.title;
+ OCA.Onlyoffice.currentWindow.document.title = config.document.title + (event.data ? " *" : "") + " - " + oc_defaults.title;
docIsChanged = event.data;
};
@@ -144,10 +147,14 @@
config.events.onRequestInsertImage = OCA.Onlyoffice.onRequestInsertImage;
config.events.onRequestMailMergeRecipients = OCA.Onlyoffice.onRequestMailMergeRecipients;
config.events.onRequestCompareFile = OCA.Onlyoffice.onRequestCompareFile;
- config.events.onRequestUsers = OCA.Onlyoffice.onRequestUsers;
config.events.onRequestSendNotify = OCA.Onlyoffice.onRequestSendNotify;
+ config.events.onRequestReferenceData = OCA.Onlyoffice.onRequestReferenceData;
config.events.onMetaChange = OCA.Onlyoffice.onMetaChange;
+ if (OC.currentUser) {
+ config.events.onRequestUsers = OCA.Onlyoffice.onRequestUsers;
+ }
+
if (!OCA.Onlyoffice.filePath) {
OCA.Onlyoffice.filePath = config._file_path;
}
@@ -205,7 +212,7 @@
};
OCA.Onlyoffice.onRequestHistoryData = function (event) {
- var version = event.data;
+ var version = event.data;
$.get(OC.generateUrl("apps/" + OCA.Onlyoffice.AppName + "/ajax/version?fileId={fileId}&version={version}",
{
@@ -476,7 +483,7 @@
};
OCA.Onlyoffice.onRequestUsers = function (event) {
- $.get(OC.generateUrl("apps/" + OCA.Onlyoffice.AppName + "/ajax/users?fileId={fileId}",
+ $.get(OC.generateUrl("apps/" + OCA.Onlyoffice.AppName + "/ajax/users?fileId={fileId}",
{
fileId: OCA.Onlyoffice.fileId || 0
}),
@@ -487,6 +494,25 @@
});
};
+ OCA.Onlyoffice.onRequestReferenceData = function (event) {
+ let referenceData = event.data.referenceData;
+ let path = event.data.path;
+
+ $.post(OC.generateUrl("apps/" + OCA.Onlyoffice.AppName + "/ajax/reference"),
+ {
+ referenceData: referenceData,
+ path: path
+ },
+ function onSuccess(response) {
+ if (response.error) {
+ OCA.Onlyoffice.showMessage(response.error, "error");
+ return;
+ }
+
+ OCA.Onlyoffice.docEditor.setReferenceData(response);
+ });
+ };
+
OCA.Onlyoffice.onRequestSendNotify = function (event) {
var actionLink = event.data.actionLink;
var comment = event.data.message;
diff --git a/js/listener.js b/js/listener.js
index 3819ccf1..03069aa7 100644
--- a/js/listener.js
+++ b/js/listener.js
@@ -81,9 +81,11 @@
}
window.addEventListener("message", function (event) {
- if ($("#onlyofficeFrame")[0].contentWindow !== event.source
- || !event.data["method"]) {
- return;
+ if ($("#onlyofficeFrame")[0]) {
+ if ($("#onlyofficeFrame")[0].contentWindow !== event.source
+ || !event.data["method"]) {
+ return;
+ }
}
switch (event.data.method) {
case "editorRequestClose":
@@ -133,6 +135,7 @@
if ($(event.target).length && $("#onlyofficeFrame").length
&& ($(event.target)[0].id === "viewer" || $(event.target)[0].id === $("#onlyofficeFrame")[0].id)) {
OCA.Onlyoffice.changeFavicon($("#onlyofficeFrame")[0].contentWindow.OCA.Onlyoffice.faviconBase);
+ window.document.title = $("#onlyofficeFrame")[0].contentWindow.OCA.Onlyoffice.titleBase;
}
});
diff --git a/js/main.js b/js/main.js
index 3872c1aa..d26ae1a2 100644
--- a/js/main.js
+++ b/js/main.js
@@ -173,7 +173,10 @@
OCA.Onlyoffice.FileClick = function (fileName, context) {
var fileInfoModel = context.fileInfoModel || context.fileList.getModelForFile(fileName);
- OCA.Onlyoffice.OpenEditor(fileInfoModel.id, context.dir, fileName);
+ var fileId = context.$file[0].dataset.id || fileInfoModel.id;
+ var winEditor = !fileInfoModel && !OCA.Onlyoffice.setting.sameTab ? document : null;
+
+ OCA.Onlyoffice.OpenEditor(fileId, context.dir, fileName, 0, winEditor);
OCA.Onlyoffice.context = context;
OCA.Onlyoffice.context.fileName = fileName;
@@ -184,7 +187,7 @@
var fileList = context.fileList;
var convertData = {
- fileId: fileInfoModel.id
+ fileId: context.$file[0].dataset.id || fileInfoModel.id
};
if ($("#isPublic").val()) {
diff --git a/js/settings.js b/js/settings.js
index 985d24d9..c3a53c31 100644
--- a/js/settings.js
+++ b/js/settings.js
@@ -63,13 +63,14 @@
var onlyofficeUrl = $("#onlyofficeUrl").val().trim();
if (!onlyofficeUrl.length) {
- $("#onlyofficeInternalUrl, #onlyofficeStorageUrl, #onlyofficeSecret").val("");
+ $("#onlyofficeInternalUrl, #onlyofficeStorageUrl, #onlyofficeSecret, #onlyofficeJwtHeader").val("");
}
var onlyofficeInternalUrl = ($("#onlyofficeInternalUrl:visible").val() || "").trim();
var onlyofficeStorageUrl = ($("#onlyofficeStorageUrl:visible").val() || "").trim();
var onlyofficeVerifyPeerOff = $("#onlyofficeVerifyPeerOff").prop("checked");
var onlyofficeSecret = ($("#onlyofficeSecret:visible").val() || "").trim();
+ var jwtHeader = ($("#onlyofficeJwtHeader:visible").val() || "").trim();
var demo = $("#onlyofficeDemo").prop("checked");
$.ajax({
@@ -81,6 +82,7 @@
storageUrl: onlyofficeStorageUrl,
verifyPeerOff: onlyofficeVerifyPeerOff,
secret: onlyofficeSecret,
+ jwtHeader: jwtHeader,
demo: demo
},
success: function onSuccess(response) {
@@ -90,8 +92,9 @@
$("#onlyofficeInternalUrl").val(response.documentserverInternal);
$("#onlyofficeStorageUrl").val(response.storageUrl);
$("#onlyofficeSecret").val(response.secret);
+ $("#onlyofficeJwtHeader").val(response.jwtHeader);
- $(".section-onlyoffice-2").toggleClass("onlyoffice-hide", (!response.documentserver.length && !demo) || !!response.error.length);
+ $(".section-onlyoffice-2").toggleClass("onlyoffice-hide", (response.documentserver == null && !demo) || !!response.error.length);
var message =
response.error
@@ -104,6 +107,8 @@
type: response.error ? "error" : null,
timeout: 3
});
+ } else {
+ $(".section-onlyoffice-2").addClass("onlyoffice-hide")
}
}
});
diff --git a/l10n/de.js b/l10n/de.js
index 83d14e60..855ee303 100644
--- a/l10n/de.js
+++ b/l10n/de.js
@@ -116,6 +116,13 @@ OC.L10N.register(
"Enable plugins": "Arbeit mit Plugins aktivieren",
"Enable document protection for": "Hinzufügen von Passwörtern in Dokumenten aktivieren für",
"All users": "Alle Benutzer",
- "Owner only": "Nur Besitzer"
+ "Owner only": "Nur Besitzer",
+ "Authorization header (leave blank to use default header)" : "Authorization-Header (leer lassen, um die standardmäßige Kopfzeile zu verwenden)",
+ "ONLYOFFICE server is not available": "ONLYOFFICE-Server ist nicht verfügbar",
+ "Please check the settings to resolve the problem.": "Bitte überprüfen Sie die Einstellungen, um das Problem zu beheben.",
+ "View settings": "Einstellungen anzeigen",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Öffnen Sie die Editoren in der Cloud einfach ohne Herunterladen und Installation",
+ "Get Now": "Jetzt erhalten"
},
"nplurals=2; plural=(n != 1);");
diff --git a/l10n/de.json b/l10n/de.json
index b2af90dc..eb5b5f90 100644
--- a/l10n/de.json
+++ b/l10n/de.json
@@ -114,6 +114,13 @@
"Enable plugins": "Arbeit mit Plugins aktivieren",
"Enable document protection for": "Hinzufügen von Passwörtern in Dokumenten aktivieren für",
"All users": "Alle Benutzer",
- "Owner only": "Nur Besitzer"
+ "Owner only": "Nur Besitzer",
+ "Authorization header (leave blank to use default header)": "Authorization-Header (leer lassen, um die standardmäßige Kopfzeile zu verwenden)",
+ "ONLYOFFICE server is not available": "ONLYOFFICE-Server ist nicht verfügbar",
+ "Please check the settings to resolve the problem.": "Bitte überprüfen Sie die Einstellungen, um das Problem zu beheben.",
+ "View settings": "Einstellungen anzeigen",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Öffnen Sie die Editoren in der Cloud einfach ohne Herunterladen und Installation",
+ "Get Now": "Jetzt erhalten"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}
\ No newline at end of file
diff --git a/l10n/de_DE.js b/l10n/de_DE.js
index 2becdc1d..fc25407c 100644
--- a/l10n/de_DE.js
+++ b/l10n/de_DE.js
@@ -116,6 +116,13 @@ OC.L10N.register(
"Enable plugins": "Arbeit mit Plugins aktivieren",
"Enable document protection for": "Hinzufügen von Passwörtern in Dokumenten aktivieren für",
"All users": "Alle Benutzer",
- "Owner only": "Nur Besitzer"
+ "Owner only": "Nur Besitzer",
+ "Authorization header (leave blank to use default header)": "Authorization-Header (leer lassen, um die standardmäßige Kopfzeile zu verwenden)",
+ "ONLYOFFICE server is not available": "ONLYOFFICE-Server ist nicht verfügbar",
+ "Please check the settings to resolve the problem.": "Bitte überprüfen Sie die Einstellungen, um das Problem zu beheben.",
+ "View settings": "Einstellungen anzeigen",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Öffnen Sie die Editoren in der Cloud einfach ohne Herunterladen und Installation",
+ "Get Now": "Jetzt erhalten"
},
"nplurals=2; plural=(n != 1);");
diff --git a/l10n/de_DE.json b/l10n/de_DE.json
index 05c7fcd9..967c3eb8 100644
--- a/l10n/de_DE.json
+++ b/l10n/de_DE.json
@@ -114,6 +114,13 @@
"Enable plugins": "Arbeit mit Plugins aktivieren",
"Enable document protection for": "Hinzufügen von Passwörtern in Dokumenten aktivieren für",
"All users": "Alle Benutzer",
- "Owner only": "Nur Besitzer"
+ "Owner only": "Nur Besitzer",
+ "Authorization header (leave blank to use default header)": "Authorization-Header (leer lassen, um die standardmäßige Kopfzeile zu verwenden)",
+ "ONLYOFFICE server is not available": "ONLYOFFICE-Server ist nicht verfügbar",
+ "Please check the settings to resolve the problem.": "Bitte überprüfen Sie die Einstellungen, um das Problem zu beheben.",
+ "View settings": "Einstellungen anzeigen",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Öffnen Sie die Editoren in der Cloud einfach ohne Herunterladen und Installation",
+ "Get Now": "Jetzt erhalten"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}
\ No newline at end of file
diff --git a/l10n/es.js b/l10n/es.js
index 28d6df4b..a2cb53eb 100644
--- a/l10n/es.js
+++ b/l10n/es.js
@@ -116,6 +116,13 @@ OC.L10N.register(
"Enable plugins": "Habilitar plugins",
"Enable document protection for": "Habilitar la protección de documentos para",
"All users": "Todos los usuarios",
- "Owner only": "Solo propietario"
+ "Owner only": "Solo propietario",
+ "Authorization header (leave blank to use default header)": "Encabezado de autenticación (dejar en blanco para utilizar el encabezado predeterminado)",
+ "ONLYOFFICE server is not available": "El servidor de ONLYOFFICE no está disponible",
+ "Please check the settings to resolve the problem.": "Por favor, compruebe la configuración para resolver el problema.",
+ "View settings": "Ver configuración",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Inicie fácilmente los editores en la nube sin tener que descargarlos ni instalarlos",
+ "Get Now": "Obtener ahora"
},
"nplurals=2; plural=(n != 1);");
diff --git a/l10n/es.json b/l10n/es.json
index a58da59f..2713e30b 100644
--- a/l10n/es.json
+++ b/l10n/es.json
@@ -114,6 +114,13 @@
"Enable plugins": "Habilitar plugins",
"Enable document protection for": "Habilitar la protección de documentos para",
"All users": "Todos los usuarios",
- "Owner only": "Solo propietario"
+ "Owner only": "Solo propietario",
+ "Authorization header (leave blank to use default header)" : "Encabezado de autenticación (dejar en blanco para utilizar el encabezado predeterminado)",
+ "ONLYOFFICE server is not available": "El servidor de ONLYOFFICE no está disponible",
+ "Please check the settings to resolve the problem.": "Por favor, compruebe la configuración para resolver el problema.",
+ "View settings": "Ver configuración",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Inicie fácilmente los editores en la nube sin tener que descargarlos ni instalarlos",
+ "Get Now": "Obtener ahora"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}
\ No newline at end of file
diff --git a/l10n/fr.js b/l10n/fr.js
index b9d60c6b..f1dfcaa9 100644
--- a/l10n/fr.js
+++ b/l10n/fr.js
@@ -65,11 +65,11 @@ OC.L10N.register(
"Insert image" : "Insérer une image",
"Select recipients" : "Sélectionner les destinataires",
"Connect to demo ONLYOFFICE Docs server" : "Se connecter à la version démo de ONLYOFFICE Docs",
- "This is a public test server, please do not use it for private sensitive data. The server will be available during a 30-day period." : "C’est un serveur public proposé à des fins de tests, veuillez ne pas l’utiliser pour vos données personnelles sensibles. Le serveur est disponible pendant 30 jours.",
- "The 30-day test period is over, you can no longer connect to demo ONLYOFFICE Docs server." : "La période d’essai de 30 jours est expirée, vous n’êtes plus en mesure de vous connecter à la version démo de ONLYOFFICE Docs.",
+ "This is a public test server, please do not use it for private sensitive data. The server will be available during a 30-day period." : "C'est un serveur public proposé à des fins de tests, veuillez ne pas l'utiliser pour vos données personnelles sensibles. Le serveur est disponible pendant 30 jours.",
+ "The 30-day test period is over, you can no longer connect to demo ONLYOFFICE Docs server." : "La période d'essai de 30 jours est expirée, vous n'êtes plus en mesure de vous connecter à la version démo de ONLYOFFICE Docs.",
"You are using public demo ONLYOFFICE Docs server. Please do not store private sensitive data." : "Vous utilisez la version démo de ONLYOFFICE Docs, proposée à des fins de tests. Veuillez ne pas stocker vos données confidentielles.",
"Select file to compare" : "Sélectionner fichier à comparer",
- "Review mode for viewing": "Mode révision lors de l’affichage",
+ "Review mode for viewing": "Mode révision lors de l'affichage",
"Markup": "Balisage",
"Final": "Finale",
"Original": "Original",
@@ -95,8 +95,8 @@ OC.L10N.register(
"File has been converted. Its content might look different.": "Le fichier a été converti. Son contenu peut s'afficher différemment.",
"Download as": "Télécharger sous",
"Download": "Télécharger",
- "Origin format": "Format d’origin",
- "Failed to send notification": "Échec de l’envoi de la notification",
+ "Origin format": "Format d'origin",
+ "Failed to send notification": "Échec de l'envoi de la notification",
"Notification sent successfully": "Notification a été envoyée avec succès",
"%1\$s mentioned in the %2\$s: \"%3\$s\".": "%1\$s vous a mentionné dans %2\$s: \"%3\$s\".",
"Choose a format to convert {fileName}": "Choisissez un format à convertir {fileName}",
@@ -116,6 +116,13 @@ OC.L10N.register(
"Enable plugins": "Activer les plugins",
"Enable document protection for": "Activer la protection des documents par mot de passe pour",
"All users": "Tous les utilisateurs",
- "Owner only": "Propriétaire uniquement"
+ "Owner only": "Propriétaire uniquement",
+ "Authorization header (leave blank to use default header)": "En-tête d'autorisation (laissez vide pour utiliser l'en-tête par défaut)",
+ "ONLYOFFICE server is not available": "Le serveur ONLYOFFICE n'est pas disponible",
+ "Please check the settings to resolve the problem.": "Veuillez vérifier les paramètres pour résoudre le problème.",
+ "View settings": "Afficher les paramètres",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Lancez facilement les éditeurs dans le cloud sans téléchargement ni installation",
+ "Get Now": "Obtenir maintenant"
},
"nplurals=2; plural=(n > 1);");
diff --git a/l10n/fr.json b/l10n/fr.json
index 34344ddd..959369dd 100644
--- a/l10n/fr.json
+++ b/l10n/fr.json
@@ -63,11 +63,11 @@
"Insert image" : "Insérer une image",
"Select recipients" : "Sélectionner les destinataires",
"Connect to demo ONLYOFFICE Docs server" : "Se connecter à la version démo de ONLYOFFICE Docs",
- "This is a public test server, please do not use it for private sensitive data. The server will be available during a 30-day period." : "C’est un serveur public proposé à des fins de tests, veuillez ne pas l’utiliser pour vos données personnelles sensibles. Le serveur est disponible pendant 30 jours.",
- "The 30-day test period is over, you can no longer connect to demo ONLYOFFICE Docs server." : "La période d’essai de 30 jours est expirée, vous n’êtes plus en mesure de vous connecter à la version démo de ONLYOFFICE Docs.",
+ "This is a public test server, please do not use it for private sensitive data. The server will be available during a 30-day period." : "C'est un serveur public proposé à des fins de tests, veuillez ne pas l'utiliser pour vos données personnelles sensibles. Le serveur est disponible pendant 30 jours.",
+ "The 30-day test period is over, you can no longer connect to demo ONLYOFFICE Docs server." : "La période d'essai de 30 jours est expirée, vous n'êtes plus en mesure de vous connecter à la version démo de ONLYOFFICE Docs.",
"You are using public demo ONLYOFFICE Docs server. Please do not store private sensitive data." : "Vous utilisez la version démo de ONLYOFFICE Docs, proposée à des fins de tests. Veuillez ne pas stocker vos données confidentielles.",
"Select file to compare" : "Sélectionner fichier à comparer",
- "Review mode for viewing": "Mode révision lors de l’affichage",
+ "Review mode for viewing": "Mode révision lors de l'affichage",
"Markup": "Balisage",
"Final": "Finale",
"Original": "Original",
@@ -93,8 +93,8 @@
"File has been converted. Its content might look different.": "Le fichier a été converti. Son contenu peut s'afficher différemment.",
"Download as": "Télécharger sous",
"Download": "Télécharger",
- "Origin format": "Format d’origin",
- "Failed to send notification": "Échec de l’envoi de la notification",
+ "Origin format": "Format d'origin",
+ "Failed to send notification": "Échec de l'envoi de la notification",
"Notification sent successfully": "Notification a été envoyée avec succès",
"%1$s mentioned in the %2$s: \"%3$s\".": "%1$s vous a mentionné dans %2$s: \"%3$s\".",
"Choose a format to convert {fileName}": "Choisissez un format à convertir {fileName}",
@@ -114,6 +114,13 @@
"Enable plugins": "Activer les plugins",
"Enable document protection for": "Activer la protection des documents par mot de passe pour",
"All users": "Tous les utilisateurs",
- "Owner only": "Propriétaire uniquement"
+ "Owner only": "Propriétaire uniquement",
+ "Authorization header (leave blank to use default header)": "En-tête d'autorisation (laissez vide pour utiliser l'en-tête par défaut)",
+ "ONLYOFFICE server is not available": "Le serveur ONLYOFFICE n'est pas disponible",
+ "Please check the settings to resolve the problem.": "Veuillez vérifier les paramètres pour résoudre le problème.",
+ "View settings": "Afficher les paramètres",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Lancez facilement les éditeurs dans le cloud sans téléchargement ni installation",
+ "Get Now": "Obtenir maintenant"
},"pluralForm" :"nplurals=2; plural=(n > 1);"
}
\ No newline at end of file
diff --git a/l10n/it.js b/l10n/it.js
index 8338b245..ae29a205 100644
--- a/l10n/it.js
+++ b/l10n/it.js
@@ -116,6 +116,13 @@ OC.L10N.register(
"Enable plugins": "Abilitare plugin",
"Enable document protection for": "Abilitare la protezione del documento per",
"All users": "Tutti gli utenti",
- "Owner only": "Solo proprietario"
+ "Owner only": "Solo proprietario",
+ "Authorization header (leave blank to use default header)": "Intestazione di autorizzazione (lascia vuoto per utilizzare l'intestazione predefinita)",
+ "ONLYOFFICE server is not available": "Il server di ONLYOFFICE non è disponibile",
+ "Please check the settings to resolve the problem.": "Ti preghiamo di controllare le impostazioni per risolvere il problema.",
+ "View settings": "Visualizza impostazioni",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs nel Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Avvia facilmente gli editor nel cloud senza scaricarli e installarli",
+ "Get Now": "Ottieni ora"
},
-"nplurals=2; plural=(n != 1);");
\ No newline at end of file
+"nplurals=2; plural=(n != 1);");
diff --git a/l10n/it.json b/l10n/it.json
index d18a8601..0a0b4694 100644
--- a/l10n/it.json
+++ b/l10n/it.json
@@ -114,6 +114,13 @@
"Enable plugins": "Abilitare plugin",
"Enable document protection for": "Abilitare la protezione del documento per",
"All users": "Tutti gli utenti",
- "Owner only": "Solo proprietario"
+ "Owner only": "Solo proprietario",
+ "Authorization header (leave blank to use default header)" : "Intestazione di autorizzazione (lascia vuoto per utilizzare l'intestazione predefinita)",
+ "ONLYOFFICE server is not available": "Il server di ONLYOFFICE non è disponibile",
+ "Please check the settings to resolve the problem.": "Ti preghiamo di controllare le impostazioni per risolvere il problema.",
+ "View settings": "Visualizza impostazioni",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs nel Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Avvia facilmente gli editor nel cloud senza scaricarli e installarli",
+ "Get Now": "Ottieni ora"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}
\ No newline at end of file
diff --git a/l10n/ja.js b/l10n/ja.js
index 315005f3..daa72df4 100644
--- a/l10n/ja.js
+++ b/l10n/ja.js
@@ -116,6 +116,13 @@ OC.L10N.register(
"Enable plugins": "プラグインを有効にする",
"Enable document protection for": "次のユーザーに対して文書の保護機能を有効にする",
"All users": "すべてのユーザー",
- "Owner only": "所有者のみ"
+ "Owner only": "所有者のみ",
+ "Authorization header (leave blank to use default header)": "認証ヘッダー (デフォルトのヘッダーを使用したい場合は空白にしてください)",
+ "ONLYOFFICE server is not available": "ONLYOFFICEサーバーは只今利用できません",
+ "Please check the settings to resolve the problem.": "問題を解決するために設定をご確認ください。",
+ "View settings": "設定を見る",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "ダウンロードやインストールをすることなく、クラウド上で簡単にエディタを起動することができます",
+ "Get Now": "今すぐ使ってみる"
},
"nplurals=1; plural=0;");
diff --git a/l10n/ja.json b/l10n/ja.json
index 45971a92..b33590db 100644
--- a/l10n/ja.json
+++ b/l10n/ja.json
@@ -114,6 +114,13 @@
"Enable plugins": "プラグインを有効にする",
"Enable document protection for": "次のユーザーに対して文書の保護機能を有効にする",
"All users": "すべてのユーザー",
- "Owner only": "所有者のみ"
+ "Owner only": "所有者のみ",
+ "Authorization header (leave blank to use default header)": "認証ヘッダー (デフォルトのヘッダーを使用したい場合は空白にしてください)",
+ "ONLYOFFICE server is not available": "ONLYOFFICEサーバーは只今利用できません",
+ "Please check the settings to resolve the problem.": "問題を解決するために設定をご確認ください。",
+ "View settings": "設定を見る",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "ダウンロードやインストールをすることなく、クラウド上で簡単にエディタを起動することができます",
+ "Get Now": "今すぐ使ってみる"
},"pluralForm" :"nplurals=1; plural=0;"
}
\ No newline at end of file
diff --git a/l10n/pt_BR.js b/l10n/pt_BR.js
index 4438622f..5a8b9747 100644
--- a/l10n/pt_BR.js
+++ b/l10n/pt_BR.js
@@ -107,12 +107,22 @@ OC.L10N.register(
"Create new Form template": "Criar novo modelo de Formulário",
"Please update ONLYOFFICE Docs to version 7.0 to work on fillable forms online": "Atualize o ONLYOFFICE Docs para a versão 7.0 para trabalhar em formulários preenchíveis online",
"Security": "Segurança",
+ "Run document macros": "Executar macros de documento",
+ "Default editor theme": "Tema do editor padrão",
"Light": "Claro",
"Classic Light": "Clássico claro",
"Dark": "Escuro",
+ "This feature is unavailable due to encryption settings.": "Este recurso não está disponível devido às configurações de criptografia.",
"Enable plugins": "Ativar plug-ins",
"Enable document protection for": "Ativar proteção de documento para",
"All users": "Todos os usuários",
- "Owner only": "Somente proprietário"
+ "Owner only": "Somente proprietário",
+ "Authorization header (leave blank to use default header)": "Cabeçalho de autorização (deixe em branco para usar o cabeçalho padrão)",
+ "ONLYOFFICE server is not available": "O servidor ONLYOFFICE não está disponível",
+ "Please check the settings to resolve the problem.": "Verifique as configurações para resolver o problema.",
+ "View settings": "Configurações de exibição",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Inicie facilmente os editores na nuvem sem download e instalação",
+ "Get Now": "Obter agora"
},
"nplurals=2; plural=(n > 1);");
diff --git a/l10n/pt_BR.json b/l10n/pt_BR.json
index 84d22e16..02352a56 100644
--- a/l10n/pt_BR.json
+++ b/l10n/pt_BR.json
@@ -105,12 +105,22 @@
"Create new Form template": "Criar novo modelo de Formulário",
"Please update ONLYOFFICE Docs to version 7.0 to work on fillable forms online": "Atualize o ONLYOFFICE Docs para a versão 7.0 para trabalhar em formulários preenchíveis online",
"Security": "Segurança",
+ "Run document macros": "Executar macros de documento",
+ "Default editor theme": "Tema do editor padrão",
"Light": "Claro",
"Classic Light": "Clássico claro",
"Dark": "Escuro",
+ "This feature is unavailable due to encryption settings.": "Este recurso não está disponível devido às configurações de criptografia.",
"Enable plugins": "Ativar plug-ins",
"Enable document protection for": "Ativar proteção de documento para",
"All users": "Todos os usuários",
- "Owner only": "Somente proprietário"
+ "Owner only": "Somente proprietário",
+ "Authorization header (leave blank to use default header)": "Cabeçalho de autorização (deixe em branco para usar o cabeçalho padrão)",
+ "ONLYOFFICE server is not available": "O servidor ONLYOFFICE não está disponível",
+ "Please check the settings to resolve the problem.": "Verifique as configurações para resolver o problema.",
+ "View settings": "Configurações de exibição",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Inicie facilmente os editores na nuvem sem download e instalação",
+ "Get Now": "Obter agora"
},"pluralForm" :"nplurals=2; plural=(n > 1);"
-}
+}
\ No newline at end of file
diff --git a/l10n/ru.js b/l10n/ru.js
index 4f698bd0..1084136a 100644
--- a/l10n/ru.js
+++ b/l10n/ru.js
@@ -116,6 +116,13 @@ OC.L10N.register(
"Enable plugins": "Включить работу с плагинами",
"Enable document protection for": "Включить возможность задавать пароль на документ для",
"All users": "Всех пользователей",
- "Owner only": "Только владельцу"
+ "Owner only": "Только владельцу",
+ "Authorization header (leave blank to use default header)": "Заголовок авторизации (оставьте пустым, чтобы использовать заголовок по умолчанию)",
+ "ONLYOFFICE server is not available": "Сервер ONLYOFFICE недоступен",
+ "Please check the settings to resolve the problem.": "Пожалуйста, проверьте настройки, чтобы решить проблему.",
+ "View settings": "Посмотреть настройки",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Легко запускайте редакторы в облаке без скачивания и установки",
+ "Get Now": "Получить сейчас"
},
"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);");
diff --git a/l10n/ru.json b/l10n/ru.json
index 13cc0fd2..49245a51 100644
--- a/l10n/ru.json
+++ b/l10n/ru.json
@@ -114,6 +114,13 @@
"Enable plugins": "Включить работу с плагинами",
"Enable document protection for": "Включить возможность задавать пароль на документ для",
"All users": "Всех пользователей",
- "Owner only": "Только владельцу"
+ "Owner only": "Только владельцу",
+ "Authorization header (leave blank to use default header)": "Заголовок авторизации (оставьте пустым, чтобы использовать заголовок по умолчанию)",
+ "ONLYOFFICE server is not available": "Сервер ONLYOFFICE недоступен",
+ "Please check the settings to resolve the problem.": "Пожалуйста, проверьте настройки, чтобы решить проблему.",
+ "View settings": "Посмотреть настройки",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE Docs Cloud",
+ "Easily launch the editors in the cloud without downloading and installation": "Легко запускайте редакторы в облаке без скачивания и установки",
+ "Get Now": "Получить сейчас"
},"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"
}
\ No newline at end of file
diff --git a/l10n/zh_CN.js b/l10n/zh_CN.js
index c9ee95f2..f732f498 100644
--- a/l10n/zh_CN.js
+++ b/l10n/zh_CN.js
@@ -107,6 +107,7 @@ OC.L10N.register(
"Create new Form template": "创建新的表单模板",
"Please update ONLYOFFICE Docs to version 7.0 to work on fillable forms online": "请将ONLYOFFICE Docs更新到7.0版本,以便在线编辑可填写的表单",
"Security": "安全",
+ "Run document macros": "运行文档宏",
"Default editor theme": "编辑器默认的主题",
"Light": "光",
"Classic Light": "经典浅色",
@@ -115,6 +116,13 @@ OC.L10N.register(
"Enable plugins": "启用插件",
"Enable document protection for": "为以下用户启用文档保护",
"All users": "所有用户",
- "Owner only": "仅限所有者"
+ "Owner only": "仅限所有者",
+ "Authorization header (leave blank to use default header)": "授权标头 (留空以使用默认的标头)",
+ "ONLYOFFICE server is not available": "ONLYOFFICE 服务器不可用",
+ "Please check the settings to resolve the problem.": "请检查设置以解决问题。",
+ "View settings": "查看设置",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE 文档云",
+ "Easily launch the editors in the cloud without downloading and installation": "无需下载和安装即可轻松启动云端编辑器",
+ "Get Now": "立即获取"
},
"nplurals=1; plural=0;");
diff --git a/l10n/zh_CN.json b/l10n/zh_CN.json
index f77782e3..dec2a79e 100644
--- a/l10n/zh_CN.json
+++ b/l10n/zh_CN.json
@@ -105,6 +105,7 @@
"Create new Form template": "创建新的表单模板",
"Please update ONLYOFFICE Docs to version 7.0 to work on fillable forms online": "请将ONLYOFFICE Docs更新到7.0版本,以便在线编辑可填写的表单",
"Security": "安全",
+ "Run document macros": "运行文档宏",
"Default editor theme": "编辑器默认的主题",
"Light": "光",
"Classic Light": "经典浅色",
@@ -113,6 +114,13 @@
"Enable plugins": "启用插件",
"Enable document protection for": "为以下用户启用文档保护",
"All users": "所有用户",
- "Owner only": "仅限所有者"
+ "Owner only": "仅限所有者",
+ "Authorization header (leave blank to use default header)": "授权标头 (留空以使用默认的标头)",
+ "ONLYOFFICE server is not available": "ONLYOFFICE 服务器不可用",
+ "Please check the settings to resolve the problem.": "请检查设置以解决问题。",
+ "View settings": "查看设置",
+ "ONLYOFFICE Docs Cloud": "ONLYOFFICE 文档云",
+ "Easily launch the editors in the cloud without downloading and installation": "无需下载和安装即可轻松启动云端编辑器",
+ "Get Now": "立即获取"
},"pluralForm" :"nplurals=1; plural=0;"
}
\ No newline at end of file
diff --git a/lib/appconfig.php b/lib/appconfig.php
index 99d44ed9..083c93a7 100644
--- a/lib/appconfig.php
+++ b/lib/appconfig.php
@@ -298,6 +298,13 @@ class AppConfig {
*/
public $_customizationPlugins = "customization_plugins";
+ /**
+ * The config key for the interval of editors availability check by cron
+ *
+ * @var string
+ */
+ private $_editors_check_interval = "editors_check_interval";
+
/**
* @param string $AppName - application name
*/
@@ -1055,20 +1062,41 @@ public function GetLimitThumbSize() {
/**
* Get the jwt header setting
*
+ * @param bool $origin - take origin
+ *
* @return string
*/
- public function JwtHeader() {
- if ($this->UseDemo()) {
+ public function JwtHeader($origin = false) {
+ if (!$origin && $this->UseDemo()) {
return $this->DEMO_PARAM["HEADER"];
}
- $header = $this->GetSystemValue($this->_jwtHeader);
+ $header = $this->config->getAppValue($this->appName, $this->_jwtHeader, "");
if (empty($header)) {
+ $header = $this->GetSystemValue($this->_jwtHeader);
+ }
+ if (!$origin && empty($header)) {
$header = "Authorization";
}
return $header;
}
+ /**
+ * Save the jwtHeader setting
+ *
+ * @param string $value - jwtHeader
+ */
+ public function SetJwtHeader($value) {
+ $value = trim($value);
+ if (empty($value)) {
+ $this->logger->info("Clear header key", ["app" => $this->appName]);
+ } else {
+ $this->logger->info("Set header key " . $value, ["app" => $this->appName]);
+ }
+
+ $this->config->setAppValue($this->appName, $this->_jwtHeader, $value);
+ }
+
/**
* Get the Jwt Leeway
*
@@ -1156,6 +1184,20 @@ public function ShareAttributesVersion() {
return "";
}
+ /**
+ * Get the editors check interval
+ *
+ * @return int
+ */
+ public function GetEditorsCheckInterval() {
+ $interval = $this->GetSystemValue($this->_editors_check_interval);
+
+ if (empty($interval)) {
+ $interval = 60*60*24;
+ }
+ return (integer)$interval;
+ }
+
/**
* Additional data about formats
*
@@ -1193,7 +1235,7 @@ public function ShareAttributesVersion() {
"txt" => [ "mime" => "text/plain", "type" => "word", "edit" => true, "editable" => true, "saveas" => ["docx", "odt", "pdf", "rtf"] ],
"xls" => [ "mime" => "application/vnd.ms-excel", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ],
"xlsm" => [ "mime" => "application/vnd.ms-excel.sheet.macroEnabled.12", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ],
- "xlsx" => [ "mime" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "type" => "cell", "edit" => true, "def" => true, "comment" => true, "modifyFilter" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ],
+ "xlsx" => [ "mime" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "type" => "cell", "edit" => true, "def" => true, "comment" => true, "modifyFilter" => true, "saveas" => ["csv", "ods", "pdf"] ],
"xlt" => [ "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ],
"xltm" => [ "mime" => "application/vnd.ms-excel.template.macroEnabled.12", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ],
"xltx" => [ "mime" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ]
@@ -1208,4 +1250,15 @@ public function ShareAttributesVersion() {
"SECRET" => "sn2puSUF7muF5Jas",
"TRIAL" => 30
];
+
+ private $linkToDocs = "https://www.onlyoffice.com/docs-registration.aspx?referer=owncloud";
+
+ /**
+ * Get link to Docs Cloud
+ *
+ * @return string
+ */
+ public function GetLinkToDocs() {
+ return $this->linkToDocs;
+ }
}
diff --git a/lib/cron/editorscheck.php b/lib/cron/editorscheck.php
new file mode 100644
index 00000000..65df9458
--- /dev/null
+++ b/lib/cron/editorscheck.php
@@ -0,0 +1,191 @@
+appName = $AppName;
+ $this->urlGenerator = $urlGenerator;
+
+ $this->logger = \OC::$server->getLogger();
+ $this->config = $config;
+ $this->trans = $trans;
+ $this->crypt = $crypt;
+ $this->groupManager = $groupManager;
+ $this->setInterval($this->config->GetEditorsCheckInterval());
+ }
+
+ /**
+ * Makes the background check
+ *
+ * @param array $argument unused argument
+ */
+ protected function run($argument) {
+ if (empty($this->config->GetDocumentServerUrl())) {
+ $this->logger->debug("Settings are empty", ["app" => $this->appName]);
+ return;
+ }
+ if (!$this->config->SettingsAreSuccessful()) {
+ $this->logger->debug("Settings are not correct", ["app" => $this->appName]);
+ return;
+ }
+ $fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.emptyfile");
+ if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) {
+ $fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl);
+ }
+ if (parse_url($fileUrl)["host"] === "localhost") {
+ $this->logger->debug("Localhost is not alowed for cron editors availability check.", ["app" => $this->appName]);
+ return;
+ }
+
+ $this->logger->debug("ONLYOFFICE check started by cron", ["app" => $this->appName]);
+
+ $documentService = new DocumentService($this->trans, $this->config);
+ list ($error, $version) = $documentService->checkDocServiceUrl($this->urlGenerator, $this->crypt);
+ if (!empty($error)) {
+ $this->logger->info("ONLYOFFICE server is not available", ["app" => $this->appName]);
+ $this->config->SetSettingsError($error);
+ $this->notifyAdmins();
+ } else {
+ $this->logger->debug("ONLYOFFICE server availability check is finished successfully", ["app" => $this->appName]);
+ }
+ }
+
+ /**
+ * Get the list of users to notify
+ *
+ * @return string[]
+ */
+ private function getUsersToNotify() {
+ $notifyGroups = ["admin"];
+ $notifyUsers = [];
+
+ foreach ($notifyGroups as $notifyGroup) {
+ $group = $this->groupManager->get($notifyGroup);
+ if ($group === null || !($group instanceof IGroup)) {
+ continue;
+ }
+ $users = $group->getUsers();
+ foreach ($users as $user) {
+ $notifyUsers[] = $user->getUID();
+ }
+ }
+ return $notifyUsers;
+ }
+
+ /**
+ * Send notification to admins
+ * @return void
+ */
+ private function notifyAdmins() {
+ $notificationManager = \OC::$server->getNotificationManager();
+ $notification = $notificationManager->createNotification();
+ $notification->setApp($this->appName)
+ ->setDateTime(new \DateTime())
+ ->setObject("editorsCheck", $this->trans->t("ONLYOFFICE server is not available"))
+ ->setSubject("editorscheck_info");
+ foreach ($this->getUsersToNotify() as $uid) {
+ $notification->setUser($uid);
+ $notificationManager->notify($notification);
+ }
+ }
+
+}
diff --git a/lib/crypt.php b/lib/crypt.php
index 7092b6b7..5b81ab40 100644
--- a/lib/crypt.php
+++ b/lib/crypt.php
@@ -50,7 +50,7 @@ public function __construct(AppConfig $appConfig) {
* @return string
*/
public function GetHash($object) {
- return \Firebase\JWT\JWT::encode($object, $this->config->GetSKey());
+ return \Firebase\JWT\JWT::encode($object, $this->config->GetSKey(), "HS256");
}
/**
@@ -67,7 +67,7 @@ public function ReadHash($token) {
return [$result, "token is empty"];
}
try {
- $result = \Firebase\JWT\JWT::decode($token, $this->config->GetSKey(), array("HS256"));
+ $result = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($this->config->GetSKey(), "HS256"));
} catch (\UnexpectedValueException $e) {
$error = $e->getMessage();
}
diff --git a/lib/documentservice.php b/lib/documentservice.php
index 65bc2baf..ead9d9d4 100644
--- a/lib/documentservice.php
+++ b/lib/documentservice.php
@@ -161,10 +161,10 @@ function SendRequestToConvertService($document_uri, $from_extension, $to_extensi
$params = [
"payload" => $data
];
- $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret());
+ $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret(), "HS256");
$opts["headers"][$this->config->JwtHeader()] = "Bearer " . $token;
- $token = \Firebase\JWT\JWT::encode($data, $this->config->GetDocumentServerSecret());
+ $token = \Firebase\JWT\JWT::encode($data, $this->config->GetDocumentServerSecret(), "HS256");
$data["token"] = $token;
$opts["body"] = json_encode($data);
}
@@ -288,10 +288,10 @@ function CommandRequest($method) {
$params = [
"payload" => $data
];
- $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret());
+ $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret(), "HS256");
$opts["headers"][$this->config->JwtHeader()] = "Bearer " . $token;
- $token = \Firebase\JWT\JWT::encode($data, $this->config->GetDocumentServerSecret());
+ $token = \Firebase\JWT\JWT::encode($data, $this->config->GetDocumentServerSecret(), "HS256");
$data["token"] = $token;
$opts["body"] = json_encode($data);
}
@@ -430,7 +430,7 @@ public function checkDocServiceUrl($urlGenerator, $crypt) {
$hashUrl = $crypt->GetHash(["action" => "empty"]);
$fileUrl = $urlGenerator->linkToRouteAbsolute(self::$appName . ".callback.emptyfile", ["doc" => $hashUrl]);
- if (!empty($this->config->GetStorageUrl())) {
+ if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) {
$fileUrl = str_replace($urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl);
}
diff --git a/lib/notifier.php b/lib/notifier.php
index 557db2bc..42773a4b 100644
--- a/lib/notifier.php
+++ b/lib/notifier.php
@@ -95,28 +95,42 @@ public function prepare($notification, $languageCode) {
}
$parameters = $notification->getSubjectParameters();
-
- $notifierId = $parameters["notifierId"];
- $fileId = $parameters["fileId"];
- $fileName = $parameters["fileName"];
- $anchor = $parameters["anchor"];
-
- $this->logger->info("Notify prepare: from $notifierId about $fileId ", ["app" => $this->appName]);
-
- $notifier = $this->userManager->get($notifierId);
- $notifierName = $notifier->getDisplayName();
$trans = $this->l10nFactory->get($this->appName, $languageCode);
- $notification->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath($this->appName, "app-dark.svg")));
- $notification->setParsedSubject($trans->t("%1\$s mentioned in the %2\$s: \"%3\$s\".", [$notifierName, $fileName, $notification->getObjectId()]));
-
- $editorLink = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".editor.index", [
- "fileId" => $fileId,
- "anchor" => $anchor
- ]);
-
- $notification->setLink($editorLink);
-
+ switch ($notification->getObjectType()) {
+ case "editorsCheck":
+ $message = $trans->t("Please check the settings to resolve the problem.");
+ $appSettingsLink = $this->urlGenerator->getAbsoluteURL("/settings/admin?sectionid=additional");
+ $notification->setLink($appSettingsLink);
+ $notification->setParsedSubject($notification->getObjectId())
+ ->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath($this->appName, 'app-dark.svg')));
+ $notification->setParsedMessage($message);
+ break;
+ case "mention":
+ $notifierId = $parameters["notifierId"];
+ $fileId = $parameters["fileId"];
+ $fileName = $parameters["fileName"];
+ $anchor = $parameters["anchor"];
+
+ $this->logger->info("Notify prepare: from $notifierId about $fileId ", ["app" => $this->appName]);
+
+ $notifier = $this->userManager->get($notifierId);
+ $notifierName = $notifier->getDisplayName();
+ $trans = $this->l10nFactory->get($this->appName, $languageCode);
+
+ $notification->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath($this->appName, "app-dark.svg")));
+ $notification->setParsedSubject($trans->t("%1\$s mentioned in the %2\$s: \"%3\$s\".", [$notifierName, $fileName, $notification->getObjectId()]));
+
+ $editorLink = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".editor.index", [
+ "fileId" => $fileId,
+ "anchor" => $anchor
+ ]);
+
+ $notification->setLink($editorLink);
+ break;
+ default:
+ $this->logger->info("Unsupported notification object: ".$notification->getObjectType(), ["app" => $this->appName]);
+ }
return $notification;
}
-}
\ No newline at end of file
+}
diff --git a/lib/preview.php b/lib/preview.php
index ef5dbb23..026da674 100644
--- a/lib/preview.php
+++ b/lib/preview.php
@@ -321,7 +321,7 @@ private function getUrl($file, $user = null, $version = 0) {
$fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.download", ["doc" => $hashUrl]);
- if (!empty($this->config->GetStorageUrl())) {
+ if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) {
$fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl);
}
diff --git a/lib/templatemanager.php b/lib/templatemanager.php
index bdcf2bf7..eff5ab69 100644
--- a/lib/templatemanager.php
+++ b/lib/templatemanager.php
@@ -194,6 +194,7 @@ public static function GetEmptyTemplatePath($lang, $ext) {
"en" => "en-US",
"en_GB" => "en-GB",
"es" => "es-ES",
+ "eu" => "eu-ES",
"fr" => "fr-FR",
"gl" => "gl-ES",
"it" => "it-IT",
@@ -205,11 +206,13 @@ public static function GetEmptyTemplatePath($lang, $ext) {
"pt_BR" => "pt-BR",
"pt_PT" => "pt-PT",
"ru" => "ru-RU",
+ "si_LK" => "si-LK",
"sk_SK" => "sk-SK",
"sv" => "sv-SE",
"tr" => "tr-TR",
"uk" => "uk-UA",
"vi" => "vi-VN",
- "zh_CN" => "zh-CN"
+ "zh_CN" => "zh-CN",
+ "zh_TW" => "zh-TW"
];
}
\ No newline at end of file
diff --git a/templates/settings.php b/templates/settings.php
index 70796d72..94a840cc 100644
--- a/templates/settings.php
+++ b/templates/settings.php
@@ -63,6 +63,9 @@
+
+
" placeholder="Authorization" type="text">
+
" placeholder="https:///" type="text">
@@ -88,6 +91,26 @@
t("The 30-day test period is over, you can no longer connect to demo ONLYOFFICE Docs server.")) ?>
+
+
+
+
+
+
ONLYOFFICE Docs Cloud
+
t("Easily launch the editors in the cloud without downloading and installation")) ?>
+
+
+
+
+
onlyoffice-hide">