Append instead of replace
This commit is contained in:
2
sign.go
2
sign.go
@@ -98,6 +98,8 @@ func main() {
|
|||||||
ContactInfo: "Geen",
|
ContactInfo: "Geen",
|
||||||
Date: time.Now().Local(),
|
Date: time.Now().Local(),
|
||||||
},
|
},
|
||||||
|
CertType: 2,
|
||||||
|
Approval: false,
|
||||||
},
|
},
|
||||||
Signer: pkey,
|
Signer: pkey,
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
|
@@ -10,8 +10,7 @@ func (context *SignContext) updateByteRange() error {
|
|||||||
// @todo: find out of this is safe.
|
// @todo: find out of this is safe.
|
||||||
output_file_stat, _ := context.OutputFile.Stat()
|
output_file_stat, _ := context.OutputFile.Stat()
|
||||||
|
|
||||||
// Don't count last newline as file length.
|
output_file_size := output_file_stat.Size()
|
||||||
output_file_size := output_file_stat.Size() - 1
|
|
||||||
|
|
||||||
// Calculate ByteRange values to replace them.
|
// Calculate ByteRange values to replace them.
|
||||||
context.ByteRangeValues = make([]int64, 4)
|
context.ByteRangeValues = make([]int64, 4)
|
||||||
@@ -20,10 +19,10 @@ func (context *SignContext) updateByteRange() error {
|
|||||||
context.ByteRangeValues[0] = int64(0)
|
context.ByteRangeValues[0] = int64(0)
|
||||||
|
|
||||||
// Signature ByteRange part 1 length always stops at the actual signature start byte.
|
// Signature ByteRange part 1 length always stops at the actual signature start byte.
|
||||||
context.ByteRangeValues[1] = context.SignatureContentsStartByte
|
context.ByteRangeValues[1] = context.SignatureContentsStartByte - 1
|
||||||
|
|
||||||
// Signature ByteRange part 2 start byte directly starts after the actual signature.
|
// Signature ByteRange part 2 start byte directly starts after the actual signature.
|
||||||
context.ByteRangeValues[2] = context.ByteRangeValues[1] + int64(signatureMaxLength)
|
context.ByteRangeValues[2] = context.ByteRangeValues[1] + 1 + int64(signatureMaxLength) + 1
|
||||||
|
|
||||||
// Signature ByteRange part 2 length is everything else of the file.
|
// Signature ByteRange part 2 length is everything else of the file.
|
||||||
context.ByteRangeValues[3] = output_file_size - context.ByteRangeValues[2]
|
context.ByteRangeValues[3] = output_file_size - context.ByteRangeValues[2]
|
||||||
|
@@ -30,7 +30,7 @@ func (context *SignContext) createCatalog() (catalog string, err error) {
|
|||||||
pages := root.Key("Pages").GetPtr()
|
pages := root.Key("Pages").GetPtr()
|
||||||
catalog += " /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R"
|
catalog += " /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R"
|
||||||
catalog += " /AcroForm <<"
|
catalog += " /AcroForm <<"
|
||||||
catalog += " /Fields [" + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R]"
|
catalog += " /Fields [" + strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 R]"
|
||||||
|
|
||||||
if !context.SignData.Signature.Approval {
|
if !context.SignData.Signature.Approval {
|
||||||
catalog += " /NeedAppearances false"
|
catalog += " /NeedAppearances false"
|
||||||
@@ -44,12 +44,11 @@ func (context *SignContext) createCatalog() (catalog string, err error) {
|
|||||||
|
|
||||||
catalog += " >>"
|
catalog += " >>"
|
||||||
|
|
||||||
// @todo: what do these do?
|
|
||||||
if !context.SignData.Signature.Approval {
|
if !context.SignData.Signature.Approval {
|
||||||
if context.SignData.Signature.CertType > 0 {
|
if context.SignData.Signature.CertType > 0 {
|
||||||
//catalog += " /Perms << /DocMDP " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>";
|
catalog += " /Perms << /DocMDP " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>";
|
||||||
} else {
|
} else {
|
||||||
//catalog += " /Perms << /UR3 " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>";
|
catalog += " /Perms << /UR3 " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -29,7 +29,7 @@ func (context *SignContext) createSignaturePlaceholder() (signature string, byte
|
|||||||
// Create a placeholder for the actual signature content, we wil replace it later.
|
// Create a placeholder for the actual signature content, we wil replace it later.
|
||||||
signature += " /Contents<" + strings.Repeat("0", int(signatureMaxLength)) + ">"
|
signature += " /Contents<" + strings.Repeat("0", int(signatureMaxLength)) + ">"
|
||||||
|
|
||||||
if context.SignData.Signature.Approval {
|
if !context.SignData.Signature.Approval {
|
||||||
signature += " /Reference [" // array of signature reference dictionaries
|
signature += " /Reference [" // array of signature reference dictionaries
|
||||||
signature += " << /Type /SigRef"
|
signature += " << /Type /SigRef"
|
||||||
if context.SignData.Signature.CertType > 0 {
|
if context.SignData.Signature.CertType > 0 {
|
||||||
@@ -77,9 +77,6 @@ func (context *SignContext) createSignature() ([]byte, error) {
|
|||||||
io.Copy(sign_buf, context.OutputFile)
|
io.Copy(sign_buf, context.OutputFile)
|
||||||
file_content := sign_buf.Bytes()
|
file_content := sign_buf.Bytes()
|
||||||
|
|
||||||
// Remove trailing newline.
|
|
||||||
file_content = file_content[:len(file_content)-1]
|
|
||||||
|
|
||||||
// Collect the parts to sign.
|
// Collect the parts to sign.
|
||||||
sign_content := make([]byte, 0)
|
sign_content := make([]byte, 0)
|
||||||
sign_content = append(sign_content, file_content[context.ByteRangeValues[0]:(context.ByteRangeValues[0]+context.ByteRangeValues[1])]...)
|
sign_content = append(sign_content, file_content[context.ByteRangeValues[0]:(context.ByteRangeValues[0]+context.ByteRangeValues[1])]...)
|
||||||
@@ -111,7 +108,7 @@ func (context *SignContext) replaceSignature() error {
|
|||||||
dst := make([]byte, hex.EncodedLen(len(signature)))
|
dst := make([]byte, hex.EncodedLen(len(signature)))
|
||||||
hex.Encode(dst, signature)
|
hex.Encode(dst, signature)
|
||||||
|
|
||||||
context.OutputFile.WriteAt(dst, context.ByteRangeValues[0]+context.ByteRangeValues[1])
|
context.OutputFile.WriteAt(dst, context.ByteRangeValues[0] + context.ByteRangeValues[1] + 1)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ func (context *SignContext) writeTrailer() error {
|
|||||||
new_root := "Root " + strconv.FormatInt(int64(context.CatalogData.ObjectId), 10) + " 0 R"
|
new_root := "Root " + strconv.FormatInt(int64(context.CatalogData.ObjectId), 10) + " 0 R"
|
||||||
|
|
||||||
size_string := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10)
|
size_string := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10)
|
||||||
new_size := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+2, 10)
|
new_size := "Size " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+3, 10)
|
||||||
|
|
||||||
trailer_string := string(trailer_buf)
|
trailer_string := string(trailer_buf)
|
||||||
trailer_string = strings.Replace(trailer_string, root_string, new_root, -1)
|
trailer_string = strings.Replace(trailer_string, root_string, new_root, -1)
|
||||||
@@ -36,7 +36,7 @@ func (context *SignContext) writeTrailer() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write PDF ending.
|
// Write PDF ending.
|
||||||
if _, err := context.OutputFile.Write([]byte("%%EOF\n")); err != nil {
|
if _, err := context.OutputFile.Write([]byte("%%EOF")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
44
sign/pdfvisualsignature.go
Normal file
44
sign/pdfvisualsignature.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package sign
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (context *SignContext) createVisualSignature() (visual_signature string, err error) {
|
||||||
|
visual_signature = strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 obj\n"
|
||||||
|
visual_signature += "<< /Type /Annot"
|
||||||
|
visual_signature += " /Subtype /Widget"
|
||||||
|
visual_signature += " /Rect [0 0 0 0]"
|
||||||
|
|
||||||
|
root := context.PDFReader.Trailer().Key("Root")
|
||||||
|
root_keys := root.Keys()
|
||||||
|
found_pages := false
|
||||||
|
for _, key := range root_keys {
|
||||||
|
if key == "Pages" {
|
||||||
|
found_pages = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_pages {
|
||||||
|
return "", errors.New("Didn't find pages in PDF trailer Root.")
|
||||||
|
}
|
||||||
|
|
||||||
|
rootPtr := root.GetPtr()
|
||||||
|
context.CatalogData.RootString = strconv.Itoa(int(rootPtr.GetID())) + " " + strconv.Itoa(int(rootPtr.GetGen())) + " R"
|
||||||
|
|
||||||
|
page := root.Key("Pages").Key("Kids").Index(0).GetPtr()
|
||||||
|
visual_signature += " /P " + strconv.Itoa(int(page.GetID())) + " " + strconv.Itoa(int(page.GetGen())) + " R"
|
||||||
|
|
||||||
|
visual_signature += " /F 4"
|
||||||
|
visual_signature += " /FT /Sig"
|
||||||
|
visual_signature += " /T " + pdfString("Signature")
|
||||||
|
visual_signature += " /Ff 0"
|
||||||
|
visual_signature += " /V " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R"
|
||||||
|
|
||||||
|
visual_signature += " >>"
|
||||||
|
visual_signature += "\nendobj\n"
|
||||||
|
|
||||||
|
return visual_signature, nil
|
||||||
|
}
|
@@ -22,7 +22,7 @@ func (context *SignContext) writeXref() error {
|
|||||||
|
|
||||||
func (context *SignContext) writeXrefTable() error {
|
func (context *SignContext) writeXrefTable() error {
|
||||||
xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10)
|
xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount, 10)
|
||||||
new_xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+2, 10)
|
new_xref_size := "xref\n0 " + strconv.FormatInt(context.PDFReader.XrefInformation.ItemCount+3, 10)
|
||||||
|
|
||||||
if _, err := context.OutputFile.Write([]byte(new_xref_size)); err != nil {
|
if _, err := context.OutputFile.Write([]byte(new_xref_size)); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -34,7 +34,16 @@ func (context *SignContext) writeXrefTable() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the new catalog xref line.
|
// Create the new catalog xref line.
|
||||||
catalog_object_start_position := strconv.FormatInt(context.PDFReader.XrefInformation.StartPos, 10)
|
visual_signature_object_start_position := strconv.FormatInt(context.Filesize, 10)
|
||||||
|
visual_signature_xref_line := leftPad(visual_signature_object_start_position, "0", 10-len(visual_signature_object_start_position)) + " 00000 n\n"
|
||||||
|
|
||||||
|
// Write the new catalog xref line.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(visual_signature_xref_line)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new catalog xref line.
|
||||||
|
catalog_object_start_position := strconv.FormatInt(context.Filesize+context.VisualSignData.Length, 10)
|
||||||
catalog_xref_line := leftPad(catalog_object_start_position, "0", 10-len(catalog_object_start_position)) + " 00000 n\n"
|
catalog_xref_line := leftPad(catalog_object_start_position, "0", 10-len(catalog_object_start_position)) + " 00000 n\n"
|
||||||
|
|
||||||
// Write the new catalog xref line.
|
// Write the new catalog xref line.
|
||||||
@@ -43,7 +52,7 @@ func (context *SignContext) writeXrefTable() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the new signature xref line.
|
// Create the new signature xref line.
|
||||||
signature_object_start_position := strconv.FormatInt(context.PDFReader.XrefInformation.StartPos+context.CatalogData.Length, 10)
|
signature_object_start_position := strconv.FormatInt(context.Filesize+context.VisualSignData.Length+context.CatalogData.Length, 10)
|
||||||
signature_xref_line := leftPad(signature_object_start_position, "0", 10-len(signature_object_start_position)) + " 00000 n\n"
|
signature_xref_line := leftPad(signature_object_start_position, "0", 10-len(signature_object_start_position)) + " 00000 n\n"
|
||||||
|
|
||||||
// Write the new signature xref line.
|
// Write the new signature xref line.
|
||||||
|
54
sign/sign.go
54
sign/sign.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"bitbucket.org/digitorus/pdf"
|
"bitbucket.org/digitorus/pdf"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CatalogData struct {
|
type CatalogData struct {
|
||||||
@@ -23,6 +24,11 @@ type SignData struct {
|
|||||||
CertificateChain []*x509.Certificate
|
CertificateChain []*x509.Certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VisualSignData struct {
|
||||||
|
ObjectId uint32
|
||||||
|
Length int64
|
||||||
|
}
|
||||||
|
|
||||||
type SignDataSignature struct {
|
type SignDataSignature struct {
|
||||||
Approval bool
|
Approval bool
|
||||||
CertType uint32
|
CertType uint32
|
||||||
@@ -38,10 +44,12 @@ type SignDataSignatureInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SignContext struct {
|
type SignContext struct {
|
||||||
|
Filesize int64
|
||||||
InputFile *os.File
|
InputFile *os.File
|
||||||
OutputFile *os.File
|
OutputFile *os.File
|
||||||
SignData SignData
|
SignData SignData
|
||||||
CatalogData CatalogData
|
CatalogData CatalogData
|
||||||
|
VisualSignData VisualSignData
|
||||||
PDFReader *pdf.Reader
|
PDFReader *pdf.Reader
|
||||||
NewXrefStart int64
|
NewXrefStart int64
|
||||||
ByteRangeStartByte int64
|
ByteRangeStartByte int64
|
||||||
@@ -73,15 +81,20 @@ func SignFile(input string, output string, sign_data SignData) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 1
|
sign_data.ObjectId = uint32(rdr.XrefInformation.ItemCount) + 2
|
||||||
|
|
||||||
|
// We do size+1 because we insert a newline.
|
||||||
context := SignContext{
|
context := SignContext{
|
||||||
|
Filesize: size + 1,
|
||||||
PDFReader: rdr,
|
PDFReader: rdr,
|
||||||
InputFile: input_file,
|
InputFile: input_file,
|
||||||
OutputFile: output_file,
|
OutputFile: output_file,
|
||||||
CatalogData: CatalogData{
|
VisualSignData: VisualSignData{
|
||||||
ObjectId: uint32(rdr.XrefInformation.ItemCount),
|
ObjectId: uint32(rdr.XrefInformation.ItemCount),
|
||||||
},
|
},
|
||||||
|
CatalogData: CatalogData{
|
||||||
|
ObjectId: uint32(rdr.XrefInformation.ItemCount) + 1,
|
||||||
|
},
|
||||||
SignData: sign_data,
|
SignData: sign_data,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,8 +107,30 @@ func SignFile(input string, output string, sign_data SignData) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (context *SignContext) SignPDF() error {
|
func (context *SignContext) SignPDF() error {
|
||||||
// Write the PDF file to the output up til the xref.
|
// Copy old file into new file.
|
||||||
if err := writePartFromSourceFileToTargetFile(context.InputFile, context.OutputFile, 0, context.PDFReader.XrefInformation.StartPos); err != nil {
|
if _, err := io.Copy(context.OutputFile, context.InputFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := context.OutputFile.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// File always needs an empty line after %%EOF.
|
||||||
|
if _, err := context.OutputFile.Write([]byte("\n")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
visual_signature, err := context.createVisualSignature()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
context.VisualSignData.Length = int64(len(visual_signature))
|
||||||
|
|
||||||
|
// Write the new catalog object.
|
||||||
|
if _, err := context.OutputFile.Write([]byte(visual_signature)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +150,8 @@ func (context *SignContext) SignPDF() error {
|
|||||||
signature_object, byte_range_start_byte, signature_contents_start_byte := context.createSignaturePlaceholder()
|
signature_object, byte_range_start_byte, signature_contents_start_byte := context.createSignaturePlaceholder()
|
||||||
|
|
||||||
// Positions are relative to old start position of xref table.
|
// Positions are relative to old start position of xref table.
|
||||||
byte_range_start_byte += context.PDFReader.XrefInformation.StartPos + int64(len(catalog))
|
byte_range_start_byte += context.Filesize + int64(len(catalog)) + int64(len(visual_signature))
|
||||||
signature_contents_start_byte += context.PDFReader.XrefInformation.StartPos + int64(len(catalog))
|
signature_contents_start_byte += context.Filesize + int64(len(catalog)) + int64(len(visual_signature))
|
||||||
|
|
||||||
context.ByteRangeStartByte = byte_range_start_byte
|
context.ByteRangeStartByte = byte_range_start_byte
|
||||||
context.SignatureContentsStartByte = signature_contents_start_byte
|
context.SignatureContentsStartByte = signature_contents_start_byte
|
||||||
@@ -127,7 +162,7 @@ func (context *SignContext) SignPDF() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the new start position of the xref table.
|
// Calculate the new start position of the xref table.
|
||||||
context.NewXrefStart = context.PDFReader.XrefInformation.StartPos + int64(len(signature_object)) + int64(len(catalog))
|
context.NewXrefStart = context.Filesize + int64(len(signature_object)) + int64(len(catalog)) + int64(len(visual_signature))
|
||||||
|
|
||||||
if err := context.writeXref(); err != nil {
|
if err := context.writeXref(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -145,5 +180,10 @@ func (context *SignContext) SignPDF() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = context.OutputFile.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/digitorus/pkcs7"
|
"github.com/digitorus/pkcs7"
|
||||||
"github.com/digitorus/timestamp"
|
"github.com/digitorus/timestamp"
|
||||||
"golang.org/x/crypto/ocsp"
|
"golang.org/x/crypto/ocsp"
|
||||||
|
"go/src/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RevocationInfoArchival struct {
|
type RevocationInfoArchival struct {
|
||||||
@@ -70,9 +71,11 @@ func Verify(file *os.File) (apiResp *Response, err error) {
|
|||||||
finfo, _ := file.Stat()
|
finfo, _ := file.Stat()
|
||||||
size := finfo.Size()
|
size := finfo.Size()
|
||||||
|
|
||||||
|
file.Seek(0, 0)
|
||||||
|
|
||||||
rdr, err := pdf.NewReader(file, size)
|
rdr, err := pdf.NewReader(file, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to open file")
|
return nil, fmt.Errorf("Failed to open file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcroForm will contain a SigFlags value if the form contains a digital signature
|
// AcroForm will contain a SigFlags value if the form contains a digital signature
|
||||||
@@ -129,6 +132,12 @@ func Verify(file *os.File) (apiResp *Response, err error) {
|
|||||||
apiResp.Error = fmt.Sprintln("Failed to get ByteRange:", i, err)
|
apiResp.Error = fmt.Sprintln("Failed to get ByteRange:", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Println(v.Key("ByteRange").Index(i-1).Int64())
|
||||||
|
log.Println(v.Key("ByteRange").Index(i).Int64())
|
||||||
|
log.Println(string(content[0:60]))
|
||||||
|
log.Println(string(content[len(content)-60:len(content)]))
|
||||||
|
log.Println(len(content))
|
||||||
|
|
||||||
p7.Content = append(p7.Content, content...)
|
p7.Content = append(p7.Content, content...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,8 +189,10 @@ func Verify(file *os.File) (apiResp *Response, err error) {
|
|||||||
signer.ValidSignature = true
|
signer.ValidSignature = true
|
||||||
signer.TrustedIssuer = false
|
signer.TrustedIssuer = false
|
||||||
}
|
}
|
||||||
|
log.Println("Invalid sig")
|
||||||
apiResp.Error = fmt.Sprintln("Failed to verify signature:", err)
|
apiResp.Error = fmt.Sprintln("Failed to verify signature:", err)
|
||||||
} else {
|
} else {
|
||||||
|
log.Println("Valid sig")
|
||||||
signer.ValidSignature = true
|
signer.ValidSignature = true
|
||||||
signer.TrustedIssuer = true
|
signer.TrustedIssuer = true
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user