forked from I2P_Developers/i2p.i2p
Partial Identicon package
Pulled from https://github.com/PauloMigAlmeida/identicon on Jan. 4, 2016, rev 96902d3c7c9733d9da4cce9c5ed424557fc2ec3c dated April 10, 2015. Contains only the files we need. Unmodified, changes to follow. License: MIT
This commit is contained in:
46
apps/imagegen/identicon/README.md
Normal file
46
apps/imagegen/identicon/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
Identicon
|
||||
=========
|
||||
|
||||
###License
|
||||
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2007-2014 Don Park <donpark@docuverse.com>
|
||||
Contributor 2014-2014 Paulo Miguel Almeida Rodenas <paulo.ubuntu@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
###Compiling and Running it
|
||||
|
||||
+ On root path run this:
|
||||
|
||||
mvn clean install
|
||||
|
||||
+ To run the example webapp run this in /webappexample folder
|
||||
|
||||
mvn clean compile package tomcat7:run -Ptomcat
|
||||
|
||||
+ Example Urls
|
||||
|
||||
http://localhost:8080/9block?code=-2034984870&size=64
|
||||
http://localhost:8080/9block?code=-2034954870&size=64
|
||||
http://localhost:8080/9block?code=-2034894870&size=64
|
||||
|
@@ -0,0 +1,11 @@
|
||||
package com.docuverse.identicon;
|
||||
|
||||
public interface IdenticonCache {
|
||||
public byte[] get(String key);
|
||||
|
||||
public void add(String key, byte[] imageData);
|
||||
|
||||
public void remove(String key);
|
||||
|
||||
public void removeAll();
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.docuverse.identicon;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* Identicon renderer interface.
|
||||
*
|
||||
* @author don
|
||||
*
|
||||
*/
|
||||
public interface IdenticonRenderer {
|
||||
|
||||
/**
|
||||
* Returns rendered identicon image for given identicon code encoded as a
|
||||
* 32-bit signed integer.
|
||||
*
|
||||
* @param code
|
||||
* identicon code
|
||||
* @param size
|
||||
* image size
|
||||
* @return identicon image
|
||||
*/
|
||||
public BufferedImage render(int code, int size);
|
||||
|
||||
/**
|
||||
* Returns rendered identicon image for given identicon code.
|
||||
*
|
||||
* @param code
|
||||
* identicon code
|
||||
* @param size
|
||||
* image size
|
||||
* @return identicon image
|
||||
*/
|
||||
public BufferedImage render(BigInteger code, int size);
|
||||
}
|
@@ -0,0 +1,169 @@
|
||||
package com.docuverse.identicon;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Utility methods useful for implementing identicon functionality. Methods are
|
||||
* class methods for convenience.
|
||||
* <p>
|
||||
* Key method of interest is {@link getIdenticonCode} which converts IP address
|
||||
* into identicon code.<br>
|
||||
* <strong>IMPORTANT</strong>: <code>inetSalt</code> value must be set to
|
||||
* reasonably long random string prior to invoking this method.
|
||||
* </p>
|
||||
*
|
||||
* @author don
|
||||
*/
|
||||
public class IdenticonUtil {
|
||||
private static final Log log = LogFactory.getLog(IdenticonUtil.class);
|
||||
|
||||
private static final int DEFAULT_IDENTICON_SIZE = 16;
|
||||
|
||||
private static final int MINIMUM_IDENTICON_SIZE = 15;
|
||||
|
||||
private static final int MAXIMUM_IDENTICON_SIZE = 64;
|
||||
|
||||
private static final int DEFAULT_INET_MASK = 0xffffffff;
|
||||
|
||||
private static int inetMask = DEFAULT_INET_MASK;
|
||||
|
||||
private static String inetSalt;
|
||||
|
||||
/**
|
||||
* Returns current IP address mask. Default is 0xffffffff.
|
||||
*
|
||||
* @return current IP address mask
|
||||
*/
|
||||
public static int getInetMask() {
|
||||
return inetMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets current IP address mask. Default is 0xffffffff.
|
||||
*
|
||||
* @param inetMask
|
||||
*/
|
||||
public static void setInetMask(int inetMask) {
|
||||
IdenticonUtil.inetMask = inetMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current inetSalt value.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getInetSalt() {
|
||||
return inetSalt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets current inetSalt value.
|
||||
*
|
||||
* @param inetSalt
|
||||
*/
|
||||
public static void setInetSalt(String inetSalt) {
|
||||
IdenticonUtil.inetSalt = inetSalt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns identicon code for given IP address.
|
||||
* <p>
|
||||
* Current implementation uses first four bytes of SHA1(int(mask(ip))+salt)
|
||||
* where mask(ip) uses inetMask to remove unwanted bits from IP address.
|
||||
* Also, since salt is a string for convenience sake, int(mask(ip)) is
|
||||
* converetd into a string and combined with inetSalt prior to hashing.
|
||||
* </p>
|
||||
*
|
||||
* @param inetAddr
|
||||
* IP address
|
||||
* @return identicon code for <code>inetAddr</code>
|
||||
* @throws Exception
|
||||
*/
|
||||
public static int getIdenticonCode(InetAddress inetAddr) throws Exception {
|
||||
if (inetSalt == null)
|
||||
throw new Exception(
|
||||
"inetSalt must be set prior to retrieving identicon code");
|
||||
|
||||
byte[] ip = inetAddr.getAddress();
|
||||
int ipInt = (((ip[0] & 0xFF) << 24) | ((ip[1] & 0xFF) << 16)
|
||||
| ((ip[2] & 0xFF) << 8) | (ip[3] & 0xFF))
|
||||
& inetMask;
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append(ipInt);
|
||||
s.append('+');
|
||||
s.append(inetSalt);
|
||||
MessageDigest md;
|
||||
md = MessageDigest.getInstance("SHA1");
|
||||
byte[] hashedIp = md.digest(s.toString().getBytes("UTF-8"));
|
||||
int code = ((hashedIp[0] & 0xFF) << 24) | ((hashedIp[1] & 0xFF) << 16)
|
||||
| ((hashedIp[2] & 0xFF) << 8) | (hashedIp[3] & 0xFF);
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns identicon code specified as an input parameter or derived from an
|
||||
* IP address.
|
||||
* <p>
|
||||
* This method is a convenience method intended to be used by servlets like
|
||||
* below:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* int code = IdenticonUtil.getIdenticonCode(request.getParameter("code"), request
|
||||
* .getRemoteAddr());
|
||||
* </pre>
|
||||
*
|
||||
* @param codeParam
|
||||
* code parameter, if <code>null</code> remoteAddr parameter
|
||||
* will be used to determine the value.
|
||||
* @param remoteAddr
|
||||
* HTTP requester's IP address. Optional if code was specified.
|
||||
* @return
|
||||
*/
|
||||
public static int getIdenticonCode(String codeParam, String remoteAddr) {
|
||||
int code = 0;
|
||||
try {
|
||||
if (codeParam != null) {
|
||||
code = Integer.parseInt(codeParam);
|
||||
} else {
|
||||
code = IdenticonUtil.getIdenticonCode(InetAddress
|
||||
.getByName(remoteAddr));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
public static int getIdenticonSize(String param) {
|
||||
int size = DEFAULT_IDENTICON_SIZE;
|
||||
try {
|
||||
String sizeParam = param;
|
||||
if (sizeParam != null) {
|
||||
size = Integer.parseInt(sizeParam);
|
||||
if (size < MINIMUM_IDENTICON_SIZE)
|
||||
size = MINIMUM_IDENTICON_SIZE;
|
||||
else if (size > MAXIMUM_IDENTICON_SIZE)
|
||||
size = MAXIMUM_IDENTICON_SIZE;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public static String getIdenticonETag(int code, int size, int version) {
|
||||
StringBuilder s = new StringBuilder("W/\"");
|
||||
s.append(Integer.toHexString(code));
|
||||
s.append('@');
|
||||
s.append(size);
|
||||
s.append('v');
|
||||
s.append(version);
|
||||
s.append('\"');
|
||||
return s.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,325 @@
|
||||
package com.docuverse.identicon;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 9-block Identicon renderer.
|
||||
*
|
||||
* <p>
|
||||
* Current implementation uses only the lower 32 bits of identicon code.
|
||||
* </p>
|
||||
*
|
||||
* @author don
|
||||
*
|
||||
*/
|
||||
public class NineBlockIdenticonRenderer2 implements IdenticonRenderer {
|
||||
|
||||
/*
|
||||
* Each patch is a polygon created from a list of vertices on a 5 by 5 grid.
|
||||
* Vertices are numbered from 0 to 24, starting from top-left corner of the
|
||||
* grid, moving left to right and top to bottom.
|
||||
*/
|
||||
|
||||
private static final int PATCH_GRIDS = 5;
|
||||
|
||||
private static final float DEFAULT_PATCH_SIZE = 20.0f;
|
||||
|
||||
private static final byte PATCH_SYMMETRIC = 1;
|
||||
|
||||
private static final byte PATCH_INVERTED = 2;
|
||||
|
||||
private static final int PATCH_MOVETO = -1;
|
||||
|
||||
private static final byte[] patch0 = { 0, 4, 24, 20 };
|
||||
|
||||
private static final byte[] patch1 = { 0, 4, 20 };
|
||||
|
||||
private static final byte[] patch2 = { 2, 24, 20 };
|
||||
|
||||
private static final byte[] patch3 = { 0, 2, 20, 22 };
|
||||
|
||||
private static final byte[] patch4 = { 2, 14, 22, 10 };
|
||||
|
||||
private static final byte[] patch5 = { 0, 14, 24, 22 };
|
||||
|
||||
private static final byte[] patch6 = { 2, 24, 22, 13, 11, 22, 20 };
|
||||
|
||||
private static final byte[] patch7 = { 0, 14, 22 };
|
||||
|
||||
private static final byte[] patch8 = { 6, 8, 18, 16 };
|
||||
|
||||
private static final byte[] patch9 = { 4, 20, 10, 12, 2 };
|
||||
|
||||
private static final byte[] patch10 = { 0, 2, 12, 10 };
|
||||
|
||||
private static final byte[] patch11 = { 10, 14, 22 };
|
||||
|
||||
private static final byte[] patch12 = { 20, 12, 24 };
|
||||
|
||||
private static final byte[] patch13 = { 10, 2, 12 };
|
||||
|
||||
private static final byte[] patch14 = { 0, 2, 10 };
|
||||
|
||||
private static final byte[] patchTypes[] = { patch0, patch1, patch2,
|
||||
patch3, patch4, patch5, patch6, patch7, patch8, patch9, patch10,
|
||||
patch11, patch12, patch13, patch14, patch0 };
|
||||
|
||||
private static final byte patchFlags[] = { PATCH_SYMMETRIC, 0, 0, 0,
|
||||
PATCH_SYMMETRIC, 0, 0, 0, PATCH_SYMMETRIC, 0, 0, 0, 0, 0, 0,
|
||||
PATCH_SYMMETRIC + PATCH_INVERTED };
|
||||
|
||||
private static int centerPatchTypes[] = { 0, 4, 8, 15 };
|
||||
|
||||
private float patchSize;
|
||||
|
||||
private GeneralPath[] patchShapes;
|
||||
|
||||
// used to center patch shape at origin because shape rotation works
|
||||
// correctly.
|
||||
private float patchOffset;
|
||||
|
||||
private Color backgroundColor = Color.WHITE;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
*/
|
||||
public NineBlockIdenticonRenderer2() {
|
||||
setPatchSize(DEFAULT_PATCH_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size in pixels at which each patch will be rendered before
|
||||
* they are scaled down to requested identicon size.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public float getPatchSize() {
|
||||
return patchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size in pixels at which each patch will be rendered before they
|
||||
* are scaled down to requested identicon size. Default size is 20 pixels
|
||||
* which means, for 9-block identicon, a 60x60 image will be rendered and
|
||||
* scaled down.
|
||||
*
|
||||
* @param size
|
||||
* patch size in pixels
|
||||
*/
|
||||
public void setPatchSize(float size) {
|
||||
this.patchSize = size;
|
||||
this.patchOffset = patchSize / 2.0f; // used to center patch shape at
|
||||
float patchScale = patchSize / 4.0f;
|
||||
// origin.
|
||||
this.patchShapes = new GeneralPath[patchTypes.length];
|
||||
for (int i = 0; i < patchTypes.length; i++) {
|
||||
GeneralPath patch = new GeneralPath(GeneralPath.WIND_NON_ZERO);
|
||||
boolean moveTo = true;
|
||||
byte[] patchVertices = patchTypes[i];
|
||||
for (int j = 0; j < patchVertices.length; j++) {
|
||||
int v = (int) patchVertices[j];
|
||||
if (v == PATCH_MOVETO)
|
||||
moveTo = true;
|
||||
float vx = ((v % PATCH_GRIDS) * patchScale) - patchOffset;
|
||||
float vy = ((float) Math.floor(((float) v) / PATCH_GRIDS))
|
||||
* patchScale - patchOffset;
|
||||
if (!moveTo) {
|
||||
patch.lineTo(vx, vy);
|
||||
} else {
|
||||
moveTo = false;
|
||||
patch.moveTo(vx, vy);
|
||||
}
|
||||
}
|
||||
patch.closePath();
|
||||
this.patchShapes[i] = patch;
|
||||
}
|
||||
}
|
||||
|
||||
public Color getBackgroundColor() {
|
||||
return backgroundColor;
|
||||
}
|
||||
|
||||
public void setBackgroundColor(Color backgroundColor) {
|
||||
this.backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
public BufferedImage render(BigInteger code, int size) {
|
||||
return renderQuilt(code.intValue(), size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns rendered identicon image for given identicon code.
|
||||
*
|
||||
* <p>
|
||||
* Size of the returned identicon image is determined by patchSize set using
|
||||
* {@link setPatchSize}. Since a 9-block identicon consists of 3x3 patches,
|
||||
* width and height will be 3 times the patch size.
|
||||
* </p>
|
||||
*
|
||||
* @param code
|
||||
* identicon code
|
||||
* @param size
|
||||
* image size
|
||||
* @return identicon image
|
||||
*/
|
||||
public BufferedImage render(int code, int size) {
|
||||
return renderQuilt(code, size);
|
||||
}
|
||||
|
||||
protected BufferedImage renderQuilt(int code, int size) {
|
||||
// -------------------------------------------------
|
||||
// PREPARE
|
||||
//
|
||||
|
||||
// decode the code into parts
|
||||
// bit 0-1: middle patch type
|
||||
// bit 2: middle invert
|
||||
// bit 3-6: corner patch type
|
||||
// bit 7: corner invert
|
||||
// bit 8-9: corner turns
|
||||
// bit 10-13: side patch type
|
||||
// bit 14: side invert
|
||||
// bit 15: corner turns
|
||||
// bit 16-20: blue color component
|
||||
// bit 21-26: green color component
|
||||
// bit 27-31: red color component
|
||||
int middleType = centerPatchTypes[code & 0x3];
|
||||
boolean middleInvert = ((code >> 2) & 0x1) != 0;
|
||||
int cornerType = (code >> 3) & 0x0f;
|
||||
boolean cornerInvert = ((code >> 7) & 0x1) != 0;
|
||||
int cornerTurn = (code >> 8) & 0x3;
|
||||
int sideType = (code >> 10) & 0x0f;
|
||||
boolean sideInvert = ((code >> 14) & 0x1) != 0;
|
||||
int sideTurn = (code >> 15) & 0x3;
|
||||
int blue = (code >> 16) & 0x01f;
|
||||
int green = (code >> 21) & 0x01f;
|
||||
int red = (code >> 27) & 0x01f;
|
||||
|
||||
// color components are used at top of the range for color difference
|
||||
// use white background for now.
|
||||
// TODO: support transparency.
|
||||
Color fillColor = new Color(red << 3, green << 3, blue << 3);
|
||||
|
||||
// outline shapes with a noticeable color (complementary will do) if
|
||||
// shape color and background color are too similar (measured by color
|
||||
// distance).
|
||||
Color strokeColor = null;
|
||||
if (getColorDistance(fillColor, backgroundColor) < 32.0f)
|
||||
strokeColor = getComplementaryColor(fillColor);
|
||||
|
||||
// -------------------------------------------------
|
||||
// RENDER
|
||||
//
|
||||
|
||||
BufferedImage targetImage = new BufferedImage(size, size,
|
||||
BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = targetImage.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
g.setBackground(backgroundColor);
|
||||
g.clearRect(0, 0, size, size);
|
||||
|
||||
float blockSize = size / 3.0f;
|
||||
float blockSize2 = blockSize * 2.0f;
|
||||
|
||||
// middle patch
|
||||
drawPatch(g, blockSize, blockSize, blockSize, middleType, 0,
|
||||
middleInvert, fillColor, strokeColor);
|
||||
|
||||
// side patchs, starting from top and moving clock-wise
|
||||
drawPatch(g, blockSize, 0, blockSize, sideType, sideTurn++, sideInvert,
|
||||
fillColor, strokeColor);
|
||||
drawPatch(g, blockSize2, blockSize, blockSize, sideType, sideTurn++,
|
||||
sideInvert, fillColor, strokeColor);
|
||||
drawPatch(g, blockSize, blockSize2, blockSize, sideType, sideTurn++,
|
||||
sideInvert, fillColor, strokeColor);
|
||||
drawPatch(g, 0, blockSize, blockSize, sideType, sideTurn++, sideInvert,
|
||||
fillColor, strokeColor);
|
||||
|
||||
// corner patchs, starting from top left and moving clock-wise
|
||||
drawPatch(g, 0, 0, blockSize, cornerType, cornerTurn++, cornerInvert,
|
||||
fillColor, strokeColor);
|
||||
drawPatch(g, blockSize2, 0, blockSize, cornerType, cornerTurn++,
|
||||
cornerInvert, fillColor, strokeColor);
|
||||
drawPatch(g, blockSize2, blockSize2, blockSize, cornerType,
|
||||
cornerTurn++, cornerInvert, fillColor, strokeColor);
|
||||
drawPatch(g, 0, blockSize2, blockSize, cornerType, cornerTurn++,
|
||||
cornerInvert, fillColor, strokeColor);
|
||||
|
||||
g.dispose();
|
||||
|
||||
return targetImage;
|
||||
}
|
||||
|
||||
private void drawPatch(Graphics2D g, float x, float y, float size,
|
||||
int patch, int turn, boolean invert, Color fillColor,
|
||||
Color strokeColor) {
|
||||
assert patch >= 0;
|
||||
assert turn >= 0;
|
||||
patch %= patchTypes.length;
|
||||
turn %= 4;
|
||||
if ((patchFlags[patch] & PATCH_INVERTED) != 0)
|
||||
invert = !invert;
|
||||
|
||||
Shape shape = patchShapes[patch];
|
||||
double scale = ((double) size) / ((double) patchSize);
|
||||
float offset = size / 2.0f;
|
||||
|
||||
// paint background
|
||||
g.setColor(invert ? fillColor : backgroundColor);
|
||||
g.fill(new Rectangle2D.Float(x, y, size, size));
|
||||
|
||||
AffineTransform savet = g.getTransform();
|
||||
g.translate(x + offset, y + offset);
|
||||
g.scale(scale, scale);
|
||||
g.rotate(Math.toRadians(turn * 90));
|
||||
|
||||
// if stroke color was specified, apply stroke
|
||||
// stroke color should be specified if fore color is too close to the
|
||||
// back color.
|
||||
if (strokeColor != null) {
|
||||
g.setColor(strokeColor);
|
||||
g.draw(shape);
|
||||
}
|
||||
|
||||
// render rotated patch using fore color (back color if inverted)
|
||||
g.setColor(invert ? backgroundColor : fillColor);
|
||||
g.fill(shape);
|
||||
|
||||
g.setTransform(savet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns distance between two colors.
|
||||
*
|
||||
* @param c1
|
||||
* @param c2
|
||||
* @return
|
||||
*/
|
||||
private float getColorDistance(Color c1, Color c2) {
|
||||
float dx = c1.getRed() - c2.getRed();
|
||||
float dy = c1.getGreen() - c2.getGreen();
|
||||
float dz = c1.getBlue() - c2.getBlue();
|
||||
return (float) Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns complementary color.
|
||||
*
|
||||
* @param color
|
||||
* @return
|
||||
*/
|
||||
private Color getComplementaryColor(Color color) {
|
||||
return new Color(color.getRGB() ^ 0x00FFFFFF);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user