SVN可视化平台(6) -- 用户授权authz和密码htpasswd修改 随机读写 RandomAccessFile

avatar 2021年12月20日15:42:20 6 2033 views
博主分享免费Java教学视频,B站账号:Java刘哥

续接上文

本文介绍如果修改 authz 文件和 htpasswd 文件

一、authz 修改

用户授权,主要是在authz里下[groups]给指定角色添加一个用户

这里造了点简单的数据

[/]
*=r
@SVN_ADMIN=rw

[groups]
SVN_ADNIN=admin

PRJ_SHU-PM=liubei
PRJ_SHU-CCB=guanyu,zhugeliang
PRJ_SHU-DEV=huangzhong,zhaoyun
PRJ_SHU-REQ=jiangwei
PRJ_SHU-TEST=weiyan


[项目:/]
*=r
@SVN_ADNIN=rw


[项目:/蜀汉项目]
*=
@SVN_ADMIN=rw
PRJ_SHU-PM=rw
PRJ_SHU-CCB=r
PRJ_SHU-DEV=r
PRJ_SHU-TEST=r

[项目:/蜀汉项目/开发库/01.项目管理]
PRJ_SHU-CCB=rw

[项目:/蜀汉项目/开发库/02.需求]
PRJ_SHU-CCB=rw
PRJ_SHU-REQ=rw
PRJ_SHU-DEV=
PRJ_SHU-TEST=

[项目:/蜀汉项目/开发库/03.设计与编码]
PRJ_SHU-DEV=rw

[项目:/蜀汉项目/开发库/04.测试]
PRJ_SHU-TEST=rw

如果我们需要给指定角色添加一个用户,如 给 PRJ_SHU-TEST添加一个 liyan

只需要按行读取 authz 文件匹配到 startWith 为 PRJ_SHU-TEST 的数据,然后替换为 PRJ_SHU-TEST=weiyan,liyan

因为 authz 文件比较大,如果项目多的话,可能会有几MB或几十MB,读取和替换很慢

解决办法是采用随机读的方法,即 RandomAccessFile,如何替换单行语句,具体我下面会介绍

 

二、htpasswd 修改

这里造了点数据

admin:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
liubei:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
guanyu:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
zhugeliang:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
huangzhong:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.

Visual SVN密码是进行加密的,加密算法是:Md5Crypt.apr1Crypt(),即MD5加盐加密,密码不可解密

在页面上修改密码,进行上面加密后,然后写入到htpasswd文件中,可以先判断用户名是否存在,如果存在则替换,否则在末尾查询。替换的方法见下文中随机读替换

 

三、RandomAccessFile 随机读替换单行

1、MatchPatternEnum

/**
 * 关键字匹配枚举
 */

public enum MatchPatternEnum {

    /**
     * equals 全匹配、包含、正则匹配规则、开始
     */
    EQUALS("equals"),CONTAIN("contains"),REGEX("regex"), STARTS_WITH("starts_with");

    private String desc;

    MatchPatternEnum(String desc){
        this.desc = desc;
    }

    public static MatchPatternEnum getEnumByDesc(String desc){
        for (MatchPatternEnum patternEnum : MatchPatternEnum.values())
        {
            if (patternEnum.desc.equals(desc))
            {
                return patternEnum;
            }
        }
        return null;
    }

    public String getDesc()
    {
        return desc;
    }

    public void setDesc(String desc)
    {
        this.desc = desc;
    }
}

 

2、PathAuth

import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * 路径权限对象
 */
public class PathAuth extends RandomIO implements IRandomObj  {

    /**
     * 路径、搜索关键词,如 [项目:/蜀汉项目] 或 liubei=
     */
    private String keyword;

    /**
     * 替换内容
     */
    private String content;

    /**
     * 匹配模式( 全匹配、模糊、正则)
     */
    private MatchPatternEnum matchPattern;

    /**
     * 结束符号
     */
    private String finishSign;

    public PathAuth(){
        //默认全匹配
        this.matchPattern = MatchPatternEnum.EQUALS;
    }


    @Override
    public String getKeyWord() {
        return this.keyword;
    }


    @Override
    public boolean replaceOneLine(OptimizedRandomAccessFile oraf, boolean needClose) throws Exception {
        Map<String, Long> posMap = searchPos(this, oraf);
        if (posMap != null && posMap.size() > 0){
            Long startPos = posMap.get("startPos");
            Long endPos = posMap.get("endPos");
            long delLength = endPos - startPos;
            StringBuilder holdContent = getHoldContent(oraf, endPos);
            oraf.seek(startPos);
            //新权限行内容-单行
            String oneLineAuth = this.getContent();
            StringBuilder replaceContent = new StringBuilder();
            replaceContent.append(oneLineAuth).append("\r\n");
            writeString(replaceContent.toString(),oraf);
            oraf.seek(startPos);
            oraf.readLine();
            long newEndPos = oraf.getFilePointer();
            long newDelLength = newEndPos - startPos;
            long needDel = delLength - newDelLength;
            oraf.write(holdContent.toString().getBytes(StandardCharsets.UTF_8));
            if( needDel > 0 ){
                long length = oraf.length();
                oraf.setLength(length-needDel);
            }
        }
        if( needClose ) {
            oraf.close();
        }
        return true;
    }

    @Override
    public boolean insertBulk(OptimizedRandomAccessFile oraf, boolean needClose) throws Exception {
        Map<String, Long> posMap = searchPos(this, oraf);
        if (posMap != null && posMap.size() > 0){
            Long endPos = posMap.get("endPos");

            StringBuilder holdContent = getHoldContent(oraf, endPos);

            //将匹配到的内容写入进文件中去
            String insertContent = this.getContent();
            //替换内容中包含\r\n
            String[] split = insertContent.split("\\\\r\\\\n");
            StringBuilder insertContentBuild = new StringBuilder();
            for (String s : split) {
                insertContentBuild.append(s).append("\r\n");
            }
            oraf.seek(endPos);
            oraf.write(insertContentBuild.toString().getBytes(StandardCharsets.UTF_8));

            oraf.write(holdContent.toString().getBytes(StandardCharsets.UTF_8));

        }
        if( needClose ) {
            oraf.close();
        }
        return true;
    }

    public String getKeyword()
    {
        return keyword;
    }

    public void setKeyword(String keyword)
    {
        this.keyword = keyword;
    }

    public String getContent()
    {
        return content;
    }

    public void setContent(String content)
    {
        this.content = content;
    }

    @Override
    public MatchPatternEnum getMatchPattern()
    {
        return matchPattern;
    }

    public void setMatchPattern(MatchPatternEnum matchPattern)
    {
        this.matchPattern = matchPattern;
    }

    @Override
    public String getFinishSign()
    {
        return finishSign;
    }

    public void setFinishSign(String finishSign)
    {
        this.finishSign = finishSign;
    }
}

 

3、IRandomObj

/**
 * 随机读取对象接口定义
 */
public interface IRandomObj {

    /**
     * 获取关键数据
     * @return
     */
    String getKeyWord();

    /**
     * 获取关键数据匹配规则
     * @return
     */
    Enum getMatchPattern();

    /**
     * 获取关键数据替换结束符
     * @return
     */
    String getFinishSign();

    /**
     * 替换指定行
     * @param oraf
     * @param needClose 是否需要关闭流
     * @return
     * @throws Exception
     */
    boolean replaceOneLine(OptimizedRandomAccessFile oraf, boolean needClose) throws Exception;

    /**
     * 在指定字符下面插入内容
     * @param oraf
     * @param needClose
     * @return
     * @throws Exception
     */
    boolean insertBulk(OptimizedRandomAccessFile oraf, boolean needClose) throws Exception;
}

 

4、RandomIO

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 随机读写类
 *
 * @author linzhuochi
 */
public class RandomIO
{
    /**
     * 写入文件
     *
     * @param s
     * @param oraf
     * @throws IOException
     */
    public void writeString(String s, OptimizedRandomAccessFile oraf) throws IOException
    {
        oraf.write(s.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 搜索关键字指定索引(关键字前、后位置)
     *
     * @param randomObj
     * @param oraf
     * @return Map<String, Long> START_POS END_POS
     * @throws IOException
     */
    public Map<String, Long> searchPos(IRandomObj randomObj, OptimizedRandomAccessFile oraf) throws Exception
    {
        String keyWord = randomObj.getKeyWord();
        String currentLine;
        long startPos = 0;
        long endPos = -1;
        long replaceEndPos = -1;
        Map<String, Long> result = null;
        Enum matchPattern = randomObj.getMatchPattern();
        String finishSign = randomObj.getFinishSign();
        boolean equals = MatchPatternEnum.EQUALS.equals(matchPattern);
        boolean contains = MatchPatternEnum.CONTAIN.equals(matchPattern);
        boolean regex = MatchPatternEnum.REGEX.equals(matchPattern);
        boolean startsWith = MatchPatternEnum.STARTS_WITH.equals(matchPattern);
        boolean findFlag = false;
        while ((currentLine = oraf.readLine()) != null)
        {
            currentLine = new String(currentLine.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            endPos = oraf.getFilePointer();
            if (regex && currentLine.matches(keyWord) || equals && currentLine.equals(keyWord)
                    || contains && currentLine.contains(keyWord) || startsWith && currentLine.startsWith(keyWord))
            {
                findFlag = true;
                break;
            }
            startPos = oraf.getFilePointer();
        }
        if (findFlag)
        {
            result = new HashMap<>();
            result.put("startPos", startPos);
            result.put("endPos", endPos);
        }

        if (findFlag && finishSign != null && !"".equals(finishSign))
        {
            replaceEndPos = endPos;
            while ((currentLine = oraf.readLine()) != null)
            {
                currentLine = new String(currentLine.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
                if (currentLine.contains(finishSign))
                {
                    break;
                }
                replaceEndPos = oraf.getFilePointer();
            }
            result.put("replaceEndPos", replaceEndPos);
        }

        return result;

    }

    /**
     * 保存暂存数据
     *
     * @param oraf
     * @return
     * @throws IOException
     */
    public StringBuilder getHoldContent(OptimizedRandomAccessFile oraf, long holdStartPos) throws IOException
    {
        oraf.seek(holdStartPos);
        String currentLine;
        StringBuilder holdContent = new StringBuilder();
        ;
        while ((currentLine = oraf.readLine()) != null)
        {
            currentLine = new String(currentLine.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            holdContent.append(currentLine).append("\r\n");
        }
        return holdContent;
    }

}

 

5、OptimizedRandomAccessFile

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;

/**
 * 优化版本随机读
 */
public class OptimizedRandomAccessFile {

    private static final int BUFFER_SIZE = 8192;
    private static int defaultExpectedLineLength = 80;
    private RandomAccessFile raf;
    private Long actualFilePointer;
    private byte[] charBuffer;
    private int nChars, nextChar;
    private int bufferSize;
    private long lastOffset;
    private boolean skipLF;

    /**
     * see {@link RandomAccessFile#RandomAccessFile(String,String)}
     *
     * @param name path to the text file
     * @param mode r, rw, rws, rwd
     * @throws FileNotFoundException
     */
    public OptimizedRandomAccessFile(String name, String mode)
            throws FileNotFoundException {
        this(name != null ? new File(name) : null, mode);
    }

    /**
     *
     * see {@link RandomAccessFile#RandomAccessFile(File,String)}
     *
     * @param file
     * @param mode
     * @throws FileNotFoundException
     */
    public OptimizedRandomAccessFile(File file, String mode)
            throws FileNotFoundException {
        this.raf = new RandomAccessFile(file, mode);
        actualFilePointer = null;
        this.bufferSize = BUFFER_SIZE;
        charBuffer = new byte[bufferSize];
    }

    /**
     * Writes
     * <code>b.length</code> bytes from the specified byte array to this file,
     * starting at the current file pointer.
     *
     * @param b the data.
     * @exception IOException if an I/O error occurs.
     */
    public synchronized void write(byte b[]) throws IOException {
        resetPosition();
        raf.write(b, 0, b.length);
    }


    // 'Random access' stuff
    /**
     * Returns the current offset in this file.
     *
     * @return the offset from the beginning of the file, in bytes, at which the
     * next read or write occurs.
     * @exception IOException if an I/O error occurs.
     */
    public synchronized long getFilePointer() throws IOException {
        if (actualFilePointer == null) {
            return raf.getFilePointer();
        } else {
            return this.actualFilePointer;
        }
    }

    /**
     * Sets the file-pointer offset, measured from the beginning of this file,
     * at which the next read or write occurs. The offset may be set beyond the
     * end of the file. Setting the offset beyond the end of the file does not
     * change the file length. The file length will change only by writing after
     * the offset has been set beyond the end of the file.
     *
     * @param pos the offset position, measured in bytes from the beginning of
     * the file, at which to set the file pointer.
     * @exception IOException if <code>pos</code> is less than <code>0</code> or
     * if an I/O error occurs.
     */
    public synchronized void seek(long pos) throws IOException {
        actualFilePointer = null;
        resetPosition();
        raf.seek(pos);
    }

    /**
     * Returns the length of this file.
     *
     * @return the length of this file, measured in bytes.
     * @exception IOException if an I/O error occurs.
     */
    public synchronized long length() throws IOException {
        return raf.length();
    }

    /**
     * Sets the length of this file.
     *
     * <p> If the present length of the file as returned by the
     * <code>length</code> method is greater than the
     * <code>newLength</code> argument then the file will be truncated. In this
     * case, if the file offset as returned by the
     * <code>getFilePointer</code> method is greater than
     * <code>newLength</code> then after this method returns the offset will be
     * equal to
     * <code>newLength</code>.
     *
     * <p> If the present length of the file as returned by the
     * <code>length</code> method is smaller than the
     * <code>newLength</code> argument then the file will be extended. In this
     * case, the contents of the extended portion of the file are not defined.
     *
     * @param newLength The desired length of the file
     * @exception IOException If an I/O error occurs
     * @since 1.2
     */
    public synchronized void setLength(long newLength) throws IOException {
        if (newLength < raf.length()) {
            resetPosition();
        }
        raf.setLength(newLength);
    }

    /**
     * Closes this random access file stream and releases any system resources
     * associated with the stream. A closed random access file cannot perform
     * input or output operations and cannot be reopened.
     *
     * <p> If this file has an associated channel then the channel is closed as
     * well.
     *
     * @exception IOException if an I/O error occurs.
     *
     * @revised 1.4
     * @spec JSR-51
     */
    public void close() throws IOException {
        raf.close();
    }
    /**
     * <p> Read the file line by line omitting the line separator. </p> <p> see
     * {@link RandomAccessFile#readLine() readLine()} and see
     * {@link java.io.BufferedReader#readLine(boolean) readLine(boolean ignoreLF)}.
     * <p>
     *
     * <p> Subsequent calls of this method are buffered. If certain other
     * methods that are affected by the current position of the reader in the
     * file is called after this method, the position is set to the start of the
     * next line and the buffer is invalidated. </p>
     *
     * <p> This method is copied from
     * {@link java.io.BufferedReader BufferedReader} with minor changes like
     * tracking position (offset) were next line starts. </p>
     *
     * @return the next line of text from this file, or null if end of file is
     * encountered before even one byte is read.
     * @exception IOException if an I/O error occurs.
     */
    public synchronized final String readLine(boolean ignoreLF) throws IOException {

        StringBuilder s = null;
        int startChar;
        int separatorIndex = 0;

        boolean omitLF = ignoreLF || skipLF;

        bufferLoop:
        for (;;) {

            if (nextChar >= nChars) {
                fill();
            }
            if (nextChar >= nChars) { /* EOF */
                if (s != null && s.length() > 0) {
                    //EOF -> hence no need to adjust position in file
                    // changed by fill()
                    return s.toString();
                } else {
                    return null;
                }
            }
            boolean eol = false;
            byte c = 0;
            int i;

            /* Skip a leftover '\n', if necessary */
            if (omitLF && ((char) charBuffer[nextChar] == '\n')) {
                nextChar++;
            }
            skipLF = false;
            omitLF = false;

            charLoop:
            for (i = nextChar; i < nChars; i++) {
                c = charBuffer[i];
                if (((char) c == '\n') || ((char) c == '\r')) {
                    eol = true;
                    break charLoop;
                }
            }

            startChar = nextChar;
            nextChar = i;

            if (eol) {
                String str;
                if (s == null) {
                    str = new String(charBuffer, startChar, i - startChar, StandardCharsets.ISO_8859_1);
                } else {
                    s.append(new String(charBuffer, startChar, i - startChar, StandardCharsets.ISO_8859_1));
                    str = s.toString();
                }
                nextChar++;
                if ((char) c == '\r') {
                    skipLF = true;
                    if (nextChar >= nChars) {
                        fill();
                    }
                    if ((char) charBuffer[nextChar] == '\n') {
                        separatorIndex = 1;
                    }
                }
                actualFilePointer = lastOffset + nextChar + separatorIndex;

                return str;
            }

            if (s == null) {
                s = new StringBuilder(defaultExpectedLineLength);
            }
            s.append(new String(charBuffer, startChar, i - startChar, StandardCharsets.ISO_8859_1)); // WORKS, ugly!
        }
    }

    /**
     * see {@link #readLine(boolean) readLine(boolean ignoreLF)}
     *
     * @return
     * @throws IOException
     */
    public synchronized String readLine() throws IOException {
        return readLine(false);
    }

    private void fill() throws IOException {

        lastOffset = raf.getFilePointer();
        actualFilePointer = lastOffset;
        byte[] buffer = new byte[bufferSize];
        int n = raf.read(buffer);
        if (n > 0) {
            nChars = n;
            nextChar = 0;
        }
        for (int i = 0; i < buffer.length; i++) {
            charBuffer[i] = buffer[i];
        }
    }


    private void resetPosition() throws IOException {
        if (actualFilePointer != null) {
            raf.seek(actualFilePointer);
            actualFilePointer = null;
        }
        nChars = 0;
        nextChar = 0;
    }
}

 

6、测试类

/**
 * 测试类
 * @author liuyanzhao
 * @date 2021/12/20 15:16
 */
public class Main
{

    public static void main(String[] args) throws Exception
    {
        // 测试在 htpasswd 里替换 liubei 为 liubei:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.
        OptimizedRandomAccessFile optimizedRandomAccessFile = new OptimizedRandomAccessFile("F:\\VisualSVN\\Repositories\\htpasswd", "rw");
        PathAuth pathAuth = new PathAuth();
        pathAuth.setMatchPattern(MatchPatternEnum.STARTS_WITH);
        pathAuth.setKeyword("liubei");
        pathAuth.setContent("liubei:$apr1$so2$Zzd6jXIxMmUhotROE5JMB.");
        pathAuth.replaceOneLine(optimizedRandomAccessFile, true);

        // 测试在 authz 在 [groups] 插入 内容块
        OptimizedRandomAccessFile optimizedRandomAccessFile2 = new OptimizedRandomAccessFile("F:\\VisualSVN\\Repositories\\authz", "rw");
        PathAuth pathAuth2 = new PathAuth();
        pathAuth2.setMatchPattern(MatchPatternEnum.STARTS_WITH);
        pathAuth2.setKeyword("[groups]");
        pathAuth2.setContent("PRJ_WEI-PM=caocao\r\n" +
                "PRJ_WEI-CCB=xiahoudun,caoren\r\n" +
                "PRJ_WEI-DEV=yejin,dianwei,juyin\r\n" +
                "PRJ_WEI-REQ=zhangliao\r\n" +
                "PRJ_WEI-TEST=guojia\r\n");
        pathAuth2.insertBulk(optimizedRandomAccessFile2, true);
    }
}

 

 

 

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
avatar

发表评论

avatar 登录者:匿名
匿名评论,评论回复后会有邮件通知

  

已通过评论:0   待审核评论数:0