mirror of
				https://github.com/JakubMelka/PDF4QT.git
				synced 2025-06-05 21:59:17 +02:00 
			
		
		
		
	Adjusting password
This commit is contained in:
		@@ -34,7 +34,7 @@ namespace pdf
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
PDFDocumentReader::PDFDocumentReader(const std::function<QString(bool*)>& getPasswordCallback) :
 | 
			
		||||
    m_successfull(true),
 | 
			
		||||
    m_result(Result::OK),
 | 
			
		||||
    m_getPasswordCallback(getPasswordCallback)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
@@ -56,13 +56,13 @@ PDFDocument PDFDocumentReader::readFromFile(const QString& fileName)
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            m_successfull = false;
 | 
			
		||||
            m_result = Result::Failed;
 | 
			
		||||
            m_errorMessage = tr("File '%1' cannot be opened for reading. %1").arg(file.errorString());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        m_successfull = false;
 | 
			
		||||
        m_result = Result::Failed;
 | 
			
		||||
        m_errorMessage = tr("File '%1' doesn't exist.").arg(fileName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -82,7 +82,7 @@ PDFDocument PDFDocumentReader::readFromDevice(QIODevice* device)
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            m_successfull = false;
 | 
			
		||||
            m_result = Result::Failed;
 | 
			
		||||
            m_errorMessage = tr("Device is not opened for reading.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -94,7 +94,7 @@ PDFDocument PDFDocumentReader::readFromDevice(QIODevice* device)
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        m_successfull = false;
 | 
			
		||||
        m_result = Result::Failed;
 | 
			
		||||
        m_errorMessage = tr("Can't open device for reading.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -246,7 +246,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
 | 
			
		||||
        {
 | 
			
		||||
            Q_ASSERT(entry.type == PDFXRefTable::EntryType::Occupied);
 | 
			
		||||
 | 
			
		||||
            if (m_successfull)
 | 
			
		||||
            if (m_result == Result::OK)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
@@ -259,7 +259,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
 | 
			
		||||
                catch (PDFParserException exception)
 | 
			
		||||
                {
 | 
			
		||||
                    QMutexLocker lock(&m_mutex);
 | 
			
		||||
                    m_successfull = false;
 | 
			
		||||
                    m_result = Result::Failed;
 | 
			
		||||
                    m_errorMessage = exception.getMessage();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -318,10 +318,15 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
 | 
			
		||||
        PDFSecurityHandlerPointer securityHandler = PDFSecurityHandler::createSecurityHandler(encryptObject, id);
 | 
			
		||||
        PDFSecurityHandler::AuthorizationResult authorizationResult = securityHandler->authenticate(m_getPasswordCallback);
 | 
			
		||||
 | 
			
		||||
        if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Failed ||
 | 
			
		||||
            authorizationResult == PDFSecurityHandler::AuthorizationResult::Cancelled)
 | 
			
		||||
        if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Cancelled)
 | 
			
		||||
        {
 | 
			
		||||
            // User cancelled the document reading
 | 
			
		||||
            m_result = Result::Cancelled;
 | 
			
		||||
            return PDFDocument();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Failed)
 | 
			
		||||
        {
 | 
			
		||||
            // TODO: If user cancels it, do not display error message.
 | 
			
		||||
            throw PDFParserException(PDFTranslationContext::tr("Authorization failed. Bad password provided."));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -340,7 +345,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
 | 
			
		||||
 | 
			
		||||
        auto processObjectStream = [this, &getObject, &objectFetcher, &objects, &objectStreamEntries] (const PDFObjectReference& objectStreamReference)
 | 
			
		||||
        {
 | 
			
		||||
            if (!m_successfull)
 | 
			
		||||
            if (m_result != Result::OK)
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@@ -423,7 +428,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
 | 
			
		||||
            catch (PDFParserException exception)
 | 
			
		||||
            {
 | 
			
		||||
                QMutexLocker lock(&m_mutex);
 | 
			
		||||
                m_successfull = false;
 | 
			
		||||
                m_result = Result::Failed;
 | 
			
		||||
                m_errorMessage = exception.getMessage();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
@@ -436,7 +441,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
 | 
			
		||||
    }
 | 
			
		||||
    catch (PDFParserException parserException)
 | 
			
		||||
    {
 | 
			
		||||
        m_successfull = false;
 | 
			
		||||
        m_result = Result::Failed;
 | 
			
		||||
        m_errorMessage = parserException.getMessage();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -445,7 +450,7 @@ PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer)
 | 
			
		||||
 | 
			
		||||
void PDFDocumentReader::reset()
 | 
			
		||||
{
 | 
			
		||||
    m_successfull = true;
 | 
			
		||||
    m_result = Result::OK;
 | 
			
		||||
    m_errorMessage = QString();
 | 
			
		||||
    m_version = PDFVersion();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,13 @@ public:
 | 
			
		||||
    constexpr inline PDFDocumentReader& operator=(const PDFDocumentReader&) = delete;
 | 
			
		||||
    constexpr inline PDFDocumentReader& operator=(PDFDocumentReader&&) = delete;
 | 
			
		||||
 | 
			
		||||
    enum class Result
 | 
			
		||||
    {
 | 
			
		||||
        OK,         ///< Document was successfully loaded
 | 
			
		||||
        Failed,     ///< Error occured during document reading
 | 
			
		||||
        Cancelled   ///< User cancelled document reading
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /// Reads a PDF document from the specified file. If file doesn't exist,
 | 
			
		||||
    /// cannot be opened or contain invalid pdf, empty PDF file is returned.
 | 
			
		||||
    /// No exception is thrown.
 | 
			
		||||
@@ -58,8 +65,8 @@ public:
 | 
			
		||||
    /// PDF is read, then empty PDF document is returned. No exception is thrown.
 | 
			
		||||
    PDFDocument readFromBuffer(const QByteArray& buffer);
 | 
			
		||||
 | 
			
		||||
    /// Returns true, if document was successfully read from device
 | 
			
		||||
    bool isSuccessfull() const { return m_successfull; }
 | 
			
		||||
    /// Returns result code for reading document from the device
 | 
			
		||||
    Result getReadingResult() const { return m_result; }
 | 
			
		||||
 | 
			
		||||
    /// Returns error message, if document reading was unsuccessfull
 | 
			
		||||
    const QString& getErrorMessage() const { return m_errorMessage; }
 | 
			
		||||
@@ -83,8 +90,8 @@ private:
 | 
			
		||||
    /// (providing thread safety)
 | 
			
		||||
    QMutex m_mutex;
 | 
			
		||||
 | 
			
		||||
    /// This bool flag is set, if pdf document was successfully read from the device
 | 
			
		||||
    std::atomic<bool> m_successfull;
 | 
			
		||||
    /// Result of document reading from the device
 | 
			
		||||
    std::atomic<Result> m_result;
 | 
			
		||||
 | 
			
		||||
    /// In case if error occurs, it is stored here
 | 
			
		||||
    QString m_errorMessage;
 | 
			
		||||
 
 | 
			
		||||
@@ -1871,6 +1871,33 @@ QString PDFEncoding::convert(const QByteArray& stream, PDFEncoding::Encoding enc
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QByteArray PDFEncoding::convertToEncoding(const QString& string, PDFEncoding::Encoding encoding)
 | 
			
		||||
{
 | 
			
		||||
    QByteArray result;
 | 
			
		||||
 | 
			
		||||
    const encoding::EncodingTable* table = getTableForEncoding(encoding);
 | 
			
		||||
    Q_ASSERT(table);
 | 
			
		||||
 | 
			
		||||
    result.reserve(string.size());
 | 
			
		||||
    for (QChar character : string)
 | 
			
		||||
    {
 | 
			
		||||
        ushort unicode = character.unicode();
 | 
			
		||||
        unsigned char converted = 0;
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < table->size(); ++i)
 | 
			
		||||
        {
 | 
			
		||||
            if (unicode == (*table)[static_cast<unsigned char>(i)])
 | 
			
		||||
            {
 | 
			
		||||
                converted = i;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        result.push_back(converted);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString PDFEncoding::convertTextString(const QByteArray& stream)
 | 
			
		||||
{
 | 
			
		||||
    if (hasUnicodeLeadMarkings(stream))
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,14 @@ public:
 | 
			
		||||
    /// \returns Converted unicode string
 | 
			
		||||
    static QString convert(const QByteArray& stream, Encoding encoding);
 | 
			
		||||
 | 
			
		||||
    /// Converts unicode string to the byte array using the specified encoding.
 | 
			
		||||
    /// It performs reverse functionality than function \p convert. If the character
 | 
			
		||||
    /// in the encoding is not found, then it is converted to character code 0.
 | 
			
		||||
    /// \param string String to be converted
 | 
			
		||||
    /// \param encoding Encoding used in the conversion
 | 
			
		||||
    /// \sa convert
 | 
			
		||||
    static QByteArray convertToEncoding(const QString& string, Encoding encoding);
 | 
			
		||||
 | 
			
		||||
    /// Convert text string to the unicode string, using either PDFDocEncoding,
 | 
			
		||||
    /// or UTF-16BE encoding. Please see PDF Reference 1.7, Chapter 3.8.1. If
 | 
			
		||||
    /// UTF-16BE encoding is used, then leading bytes should be 0xFE and 0xFF
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@
 | 
			
		||||
 | 
			
		||||
#include "pdfsecurityhandler.h"
 | 
			
		||||
#include "pdfexception.h"
 | 
			
		||||
#include "pdfencoding.h"
 | 
			
		||||
 | 
			
		||||
#include <openssl/rc4.h>
 | 
			
		||||
#include <openssl/md5.h>
 | 
			
		||||
@@ -369,8 +370,8 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate
 | 
			
		||||
                        AES_KEY key = { };
 | 
			
		||||
                        AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(fileEncryptionDecryptionKey.data()), fileEncryptionDecryptionKey.size() * 8, &key);
 | 
			
		||||
                        unsigned char aesInitializationVector[AES_BLOCK_SIZE] = { };
 | 
			
		||||
                        m_authorizationData.fileEncryptionKey.resize(m_OE.size());
 | 
			
		||||
                        AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(m_OE.data()), reinterpret_cast<unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_OE.size(), &key, aesInitializationVector, AES_DECRYPT);
 | 
			
		||||
                        m_authorizationData.fileEncryptionKey.resize(m_UE.size());
 | 
			
		||||
                        AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(m_UE.data()), reinterpret_cast<unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_UE.size(), &key, aesInitializationVector, AES_DECRYPT);
 | 
			
		||||
 | 
			
		||||
                        // We have authorized owner access
 | 
			
		||||
                        m_authorizationData.authorizationResult =  AuthorizationResult::UserAuthorized;
 | 
			
		||||
@@ -380,6 +381,36 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate
 | 
			
		||||
                // Stop, if we authorized the document usage
 | 
			
		||||
                if (m_authorizationData.isAuthorized())
 | 
			
		||||
                {
 | 
			
		||||
                    // According the PDF specification, we must also check, if flags are not manipulated.
 | 
			
		||||
                    Q_ASSERT(m_Perms.size() == AES_BLOCK_SIZE);
 | 
			
		||||
                    AES_KEY key = { };
 | 
			
		||||
                    AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(m_authorizationData.fileEncryptionKey.data()), m_authorizationData.fileEncryptionKey.size() * 8, &key);
 | 
			
		||||
                    QByteArray decodedPerms(m_Perms.size(), char(0));
 | 
			
		||||
                    AES_ecb_encrypt(reinterpret_cast<const unsigned char*>(m_Perms.data()), reinterpret_cast<unsigned char*>(decodedPerms.data()), &key, AES_DECRYPT);
 | 
			
		||||
 | 
			
		||||
                    // 1) Checks, if bytes 9, 10, 11 are 'a', 'd', 'b'
 | 
			
		||||
                    if (decodedPerms[9] != 'a' || decodedPerms[10] != 'd' || decodedPerms[11] != 'b')
 | 
			
		||||
                    {
 | 
			
		||||
                        throw PDFParserException(PDFTranslationContext::tr("Permissions entry in the Encryption dictionary is invalid."));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // 2) Verify, that bytes 0-3 are valid permissions entry
 | 
			
		||||
                    const uint32_t permissions = qFromLittleEndian(*reinterpret_cast<const uint32_t*>(decodedPerms.data()));
 | 
			
		||||
                    if (permissions != m_permissions)
 | 
			
		||||
                    {
 | 
			
		||||
                        throw PDFParserException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document."));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // 3) Verify, that byte 8 is 'T' or 'F' and is equal to EncryptMetadata entry
 | 
			
		||||
                    if (decodedPerms[8] != 'T' && decodedPerms[8] != 'F')
 | 
			
		||||
                    {
 | 
			
		||||
                        throw PDFParserException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document."));
 | 
			
		||||
                    }
 | 
			
		||||
                    if ((decodedPerms[8] == 'T') != m_encryptMetadata)
 | 
			
		||||
                    {
 | 
			
		||||
                        throw PDFParserException(PDFTranslationContext::tr("Security permissions are manipulated. Can't open the document."));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return m_authorizationData.authorizationResult;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -390,8 +421,7 @@ PDFSecurityHandler::AuthorizationResult PDFStandardSecurityHandler::authenticate
 | 
			
		||||
                return AuthorizationResult::Failed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: Handle passwords better - in some revisions, must be in PDFDocEncoding!
 | 
			
		||||
        password = getPasswordCallback(&passwordObtained).toUtf8();
 | 
			
		||||
        password = adjustPassword(getPasswordCallback(&passwordObtained));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return AuthorizationResult::Cancelled;
 | 
			
		||||
@@ -767,4 +797,114 @@ PDFStandardSecurityHandler::UserOwnerData_r6 PDFStandardSecurityHandler::parsePa
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QByteArray PDFStandardSecurityHandler::adjustPassword(const QString& password)
 | 
			
		||||
{
 | 
			
		||||
    QByteArray result;
 | 
			
		||||
 | 
			
		||||
    switch (m_R)
 | 
			
		||||
    {
 | 
			
		||||
        case 2:
 | 
			
		||||
        case 3:
 | 
			
		||||
        case 4:
 | 
			
		||||
        {
 | 
			
		||||
            // According to the PDF specification, convert string to PDFDocEncoding encoding
 | 
			
		||||
            result = PDFEncoding::convertToEncoding(password, PDFEncoding::Encoding::PDFDoc);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case 6:
 | 
			
		||||
        {
 | 
			
		||||
            // According to the PDF specification, use SASLprep profile for stringprep RFC 4013, please see these websites:
 | 
			
		||||
            //      - RFC 4013: https://tools.ietf.org/html/rfc4013 (SASLprep profile for stringprep algorithm)
 | 
			
		||||
            //      - RFC 3454: https://tools.ietf.org/html/rfc3454 (stringprep algorithm - preparation of internationalized strings)
 | 
			
		||||
            //
 | 
			
		||||
            // Note: we don't do checks according the RFC 4013, just use the mapping and normalize string in KC
 | 
			
		||||
 | 
			
		||||
            QString preparedPassword;
 | 
			
		||||
            preparedPassword.reserve(password.size());
 | 
			
		||||
 | 
			
		||||
            // RFC 4013 Section 2.1, use mapping
 | 
			
		||||
 | 
			
		||||
            for (const QChar character : password)
 | 
			
		||||
            {
 | 
			
		||||
                if (isUnicodeMappedToNothing(character.unicode()))
 | 
			
		||||
                {
 | 
			
		||||
                    // Mapped to nothing
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (isUnicodeNonAsciiSpaceCharacter(character.unicode()))
 | 
			
		||||
                {
 | 
			
		||||
                    // Map to space character
 | 
			
		||||
                    preparedPassword += QChar(QChar::Space);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    preparedPassword += character;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // RFC 4013, Section 2.2, normalization to KC
 | 
			
		||||
            preparedPassword = preparedPassword.normalized(QString::NormalizationForm_KC);
 | 
			
		||||
 | 
			
		||||
            // We don't do other checks. We will transform password to the UTF-8 encoding
 | 
			
		||||
            // and according the PDF specification, we take only first 127 characters.
 | 
			
		||||
            result = preparedPassword.toUtf8().left(127);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PDFStandardSecurityHandler::isUnicodeNonAsciiSpaceCharacter(ushort unicode)
 | 
			
		||||
{
 | 
			
		||||
    switch (unicode)
 | 
			
		||||
    {
 | 
			
		||||
        case 0x00A0:
 | 
			
		||||
        case 0x1680:
 | 
			
		||||
        case 0x2000:
 | 
			
		||||
        case 0x2001:
 | 
			
		||||
        case 0x2002:
 | 
			
		||||
        case 0x2003:
 | 
			
		||||
        case 0x2004:
 | 
			
		||||
        case 0x2005:
 | 
			
		||||
        case 0x2006:
 | 
			
		||||
        case 0x2007:
 | 
			
		||||
        case 0x2008:
 | 
			
		||||
        case 0x2009:
 | 
			
		||||
        case 0x200A:
 | 
			
		||||
        case 0x200B:
 | 
			
		||||
        case 0x202F:
 | 
			
		||||
        case 0x205F:
 | 
			
		||||
        case 0x3000:
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PDFStandardSecurityHandler::isUnicodeMappedToNothing(ushort unicode)
 | 
			
		||||
{
 | 
			
		||||
    switch (unicode)
 | 
			
		||||
    {
 | 
			
		||||
        case 0x00AD:
 | 
			
		||||
        case 0x034F:
 | 
			
		||||
        case 0x1806:
 | 
			
		||||
        case 0x180B:
 | 
			
		||||
        case 0x180C:
 | 
			
		||||
        case 0x180D:
 | 
			
		||||
        case 0x200B:
 | 
			
		||||
        case 0x200C:
 | 
			
		||||
        case 0x200D:
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}   // namespace pdf
 | 
			
		||||
 
 | 
			
		||||
@@ -176,6 +176,19 @@ private:
 | 
			
		||||
    /// Parses parts of the user/owner data (U/O values of the encryption dictionary)
 | 
			
		||||
    UserOwnerData_r6 parseParts(const QByteArray& data) const;
 | 
			
		||||
 | 
			
		||||
    /// Adjusts the password according to the PDF specification
 | 
			
		||||
    QByteArray adjustPassword(const QString& password);
 | 
			
		||||
 | 
			
		||||
    /// Returns true, if character with unicode code is non-ascii space character
 | 
			
		||||
    /// according the RFC 3454, section C.1.2
 | 
			
		||||
    /// \param unicode Unicode code to be tested
 | 
			
		||||
    static bool isUnicodeNonAsciiSpaceCharacter(ushort unicode);
 | 
			
		||||
 | 
			
		||||
    /// Returns true, if character with unicode code is mapped to nothing,
 | 
			
		||||
    /// according the RFC 3454, section B.1
 | 
			
		||||
    /// \param unicode Unicode code to be tested
 | 
			
		||||
    static bool isUnicodeMappedToNothing(ushort unicode);
 | 
			
		||||
 | 
			
		||||
    /// Revision number of standard security number
 | 
			
		||||
    int m_R = 0;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user