end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

wget for linux でサイトをクローリング(スクレイピング?)する際の自分流オプション

おおよそ、次のようなオプションでdownloadすることで、 localにサイトのクローンを作成できてきます。

最近の世の中には、サイトクロールを行う高機能?なツールも存在すると思いますが、 wgetも十分すぎる程、高機能で、以下の他にも大量のoptionがあります

$ wget --version
  GNU Wget 1.19.5 built on linux-gnu.
$ nohup \
    wget
      --mirror                   \ = --recursive & --level inf & -–timestamping 
      --page-requisites          \ pageが使用する画像,css,jsもdownload
      --no-clobber               \ download済fileを上書きせず、別名で保存
      --restrict-file-names=unix \ 日本語file名の文字化け防止 [unix|windows]
      --convert-links            \ localのfileでも閲覧できるよう相対path化
      --adjust-extension         \ .htmlがない場合の拡張子付与や?パラメータ削除
      --random-wait              \
      --no-parent                \
      --no-host-directories      \
      --quiet --show-progress    \ tail -f で進捗を眺める際、適度な情報量
      --execute robots=off       \ 
      www.ないしょ.com &

「--adjust-extension」を使用する場合、wgetの処理完了までの間、 file名が「RGPINH~2」のようなrandomな名称になっている気がします。

情報銀行 - 情報信託機能の認定スキームの在り方に関する検討会

情報銀行」って、その単語自体は分かりやすいのですが、何よりユースケースが思い浮かばない。

総務省の文書を読み返してみましたが、やっぱり理解できない。

ブロックチェーン同様、流行り言葉に向かう気がします。

本エントリと直接の関係はありませんが、 欧州の GDPR (General Data Protection Regulation:一般データ保護規則) は、「GDPR」の用語を忘れるのでメモしておきます。

python で smb 接続し、dir以下を再帰探索

以下のように書くと、よさそう

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

import datetime
import os
import sys
import platform
from smb.SMBConnection import SMBConnection
import time

conf = {"user_id":"ないしょ",
        "user_pw":"ないしょ",
        "remote_netbios":'CBBないしょ4252',
        "remote_ip":"ないしょ",
#        "remote_port":139,
        "remote_port":445,      # tera station?に対しては
        "is_direct_tcp": True,  #ここがTrue & port445がよさそう
        "remote_path":"ファイルサーバー",
        "sleep": 0.2}

def main():
    smb_conn = SMBConnection(conf["user_id"],
                             conf["user_pw"],
                             platform.uname().node, # client netbios
                             conf["remote_netbios"],
                             is_direct_tcp=conf["is_direct_tcp"])
    try:
        smb_conn.connect(conf["remote_ip"],conf["remote_port"])
    except Exception as e:
        print('Connect failed. Reason: %s', e)
        return False

    start_dir = ""
    if len(sys.argv) == 2:
        start_dir = sys.argv[1]
    else:
        print("USAGE:",sys.argv[0],"start_dir")
        return None

    files_summary = {}
    aggregate_dir( smb_conn, start_dir, files_summary)
    
    smb_conn.close()

    sorted_summary = sorted(files_summary.items(), key=lambda x:x[0])
    for summary in sorted_summary:
        yyyy,file_type = summary[0].split("\t")
        disp_str = "\t".join([str(yyyy),
                              file_type,
                              str(summary[1]["byte"]),
                              str(summary[1]["count"]) ])
        print( disp_str)
    

    

def aggregate_dir( smb_conn, org_dir, files_summary ):
    print("aggregating ...", org_dir)
    time.sleep(conf["sleep"])
    
    dir_or_files = smb_conn.listPath(conf["remote_path"],org_dir)

    for dir_or_file in dir_or_files:
        if dir_or_file.filename=="." or dir_or_file.filename=="..":
            continue
        
        if dir_or_file.isDirectory:
            next_dir = org_dir +"/"+ dir_or_file.filename
            aggregate_dir( smb_conn, next_dir, files_summary)
            continue

        last_unix_time = dir_or_file.last_write_time
        if last_unix_time < dir_or_file.last_access_time:
            last_unix_time = dir_or_file.last_access_time
            
        atime = date_str_from_unixtime(last_unix_time)
        file_ext = dir_or_file.filename.split(".")[-1].upper()
        summary_key = atime +"\t"+file_ext
        
        if (summary_key in files_summary) == False:
            files_summary[summary_key] = {"byte":0, "count":0}

        files_summary[summary_key]["count"] += 1
        files_summary[summary_key]["byte"]  += dir_or_file.file_size

        
def date_str_from_unixtime(org_val):
    dt = datetime.datetime.fromtimestamp(org_val)
#    print(dt.strftime('%Y-%m-%d %H:%M:%S'))
    return dt.strftime('%Y')
    
if __name__ == '__main__':
    main()

install modsecurity ver.2.9.3 (FOSS WAF) from source to apache 2.4 + centos8

ファイアウォール系の仕組みは、実運用に向けたルール選定や設定が非常に難しい。

特にWAFは単純導入だけでは、検知できないケースがあるだけでなく、過検知によりサービス不能に陥る。

2010年のIPAによる「オープンソースWAF「ModSecurity」導入事例 ~ IPA はこう考えた ~」公開も こういった背景があるのかと思います。

ただ、全く触ったこともないと、今後の議論や検討を継続できない為、以下、手習いメモ。

参考URL

install mod security

$ wget https://www.modsecurity.org/tarball/2.9.3/modsecurity-2.9.3.tar.gz
$ tar -xvf modsecurity-2.9.3.tar.gz
$ cd modsecurity-2.9.3
$ ./configure --prefix=/home/end0tknr/local/modsecurity \
              --with-apxs=/home/end0tknr/local/apache/bin/apxs \
              --with-apr=/home/end0tknr/local/apache/bin/apr-1-config \
              --with-apu=/home/end0tknr/local/apache/bin/apu-1-config
$ make
$ make check
$ make install 

$ cp modsecurity.conf-recommended \
     /home/end0tknr/local/apache/conf/extra/modsecurity.conf
$ cp unicode.mapping /home/end0tknr/local/apache/conf/extra/ ※1

※1 以下のようなerrorとなる為

$ ./bin/apachectl start
AH00526: Syntax error on line 219 of apache/conf/extra/modsecurity.conf:
Could not open unicode map file "apache/conf/extra/unicode.mapping"

install OWASP ModSecurity CRS(Core Rule Set)

こちらは、ダウンロード&解凍のみ

$ cd /home/end0tknr/local/apache/conf
$ wget https://github.com/SpiderLabs/owasp-modsecurity-crs/archive/v3.2.0.tar.gz
$ tar -xvf v3.2.0.tar.gz
$ ln -s owasp-modsecurity-crs-3.2.0 modsecurity
$ cp modsecurity/crs-setup.conf.example modsecurity/crs-setup.conf

config apache and mod_security

$ vi /home/end0tknr/local/apache/conf/httpd.conf
LoadModule unique_id_module modules/mod_unique_id.so
LoadModule security2_module modules/mod_security2.so

<IfModule security2_module>
Include conf/extra/modsecurity.conf
</IfModule>
$ vi /home/end0tknr/local/apache/conf/extra/modsecurity.conf
old) /var/log/modsec_audit.log
new) logs/modsec_audit.log

old) #SecDebugLog /opt/modsecurity/var/log/debug.log
old) #SecDebugLogLevel 3
new) SecDebugLog logs/modsec_debug.log
new) SecDebugLogLevel 3

old) SecPcreMatchLimit 1000
old) SecPcreMatchLimitRecursion 1000
new) SecPcreMatchLimit 5000000
new) SecPcreMatchLimitRecursion 5000000 ※2

add) Include conf/modsecurity/crs-setup.conf 
add) Include conf/modsecurity/rules/*.conf ※3

※2 default値が小さく、以下のように error_log に「Execution error - PCRE limits exceeded (-8)」と表示された為

[:error] [pid 10784:tid 139973680060160] [client 192.168.63.1:42265] [client 192.168.63.1]
  ModSecurity: Rule 17ba660 [id "-"]
  [file "/home/end0tknr/local/apache/conf/modsecurity/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf"]
  [line "342"] - Execution error - PCRE limits exceeded (-8): (null). [hostname "cent80.a5.jp"]
  [uri "/Test/test.php"] [unique_id "Xsl37TWWTWnMNiW9xIdZDAAAAIQ"]```

※3 採用するルールは厳選が必要です


# 動作確認

modsecurity.conf で「SecRuleEngine DetectionOnly→On」と設定し、
「http://cent80.a5.jp:8080/test.php?union+select」のようにアクセスし、
「403 Forbidden」と表示されれば、modsecurityは動作しています。

install clamav-0.102.3 from src to centos 8

過去、数回installしていますが、これまでのメモが不十分でしたので、再度、お試し

参考url

install

主に https://clamav-jp.osdn.jp/jdoc/clamav.html の記載の通りです。

$ sudo groupadd clamav
$ sudo useradd -g clamav -s /bin/false -c "Clam Antivirus" clamav
$ sudo yum install gmp
$ wget https://www.clamav.net/downloads/production/clamav-0.102.3.tar.gz
$ tar -xvf clamav-0.102.3.tar.gz
$ ./configure
  :
configure: Summary of detected features follows
              OS          : linux-gnu
              pthreads    : yes (-lpthread)
configure: Summary of miscellaneous features
              check       : no (auto)
              fanotify    : yes
              fdpassing   : 1
              IPv6        : yes
              openssl     : /usr
              libcurl     : /usr/local
configure: Summary of optional tools
              clamdtop    : yes (-lncurses)
              milter      : no (missing libmilter) (disabled)
              clamsubmit  : no (missing libjson-c-dev. Use the website to submit FPs/FNs.) (disabled)
              clamonacc   : yes (auto)
configure: Summary of engine performance features
              release mode: yes
              llvm        : no (disabled)
              mempool     : yes
configure: Summary of engine detection features
              iconv       : yes
              bzip2       : ok
              zlib        : yes (from system)
              unrar       : yes
              preclass    : no (missing libjson-c-dev) (disabled)
              pcre        : /usr/local
              libmspack   : yes (Internal)
              libxml2     : yes, from /usr/local
              yara        : yes
              fts         : yes (libc)
$ make
$ make check
$ sudo make install 

virus 定期 databaseの作成

$ sudo /usr/local/bin/freshclam
ERROR: Can't open/parse the config file /usr/local/etc/freshclam.conf

$ sudo cp ~/tmp/clamav-0.102.3/etc/freshclam.conf.sample /usr/local/etc/freshclam.conf

が、freshclam.conf 内に「Example」という記述があったので、コメント化

$ sudo /usr/local/bin/freshclam
ERROR: Please edit the example config file /usr/local/etc/freshclam.conf
ERROR: Can't open/parse the config file /usr/local/etc/freshclam.conf

$ sudo vi /usr/local/etc/freshclam.conf

  # Comment or remove the line below.
  # Example ←ココをコメント化

更に、virus定義file 用のdirがない為、mkdir

$ sudo /usr/local/bin/freshclam
ERROR: Database directory does not exist: /usr/local/share/clamav/
ERROR: initialize: libfreshclam init failed.
ERROR: Initialization error!

$ sudo mkdir /usr/local/share/clamav

更にx2、ユーザ(clamav)への書込み権限が必要らしく、chown

$ sudo /usr/local/bin/freshclam
ClamAV update process started at Sat May 23 10:54:10 2020
ERROR: Can't create temporary directory /usr/local/share/clamav/tmp.bbd17
Hint: The database directory must be writable for UID 1005 or GID 1007
ERROR: Update failed.

$ sudo chown clamav:clamav /usr/local/share/clamav
$ ls -l /usr/local/share
   :
drwxr-xr-x   2 clamav clamav    6 May 23 10:54 clamav
$

で、やっと成功。

$ sudo /usr/local/bin/freshclam
ClamAV update process started at Sat May 23 11:00:01 2020
daily database available for download (remote version: 25820)
Time: 17.6s, ETA: 0.0s [=============================>] 63.14MiB/63.14MiB        
Testing database: '/usr/local/share/clamav/tmp.3cc97/clamav-f27aa8b7c8f1244ca8cb7be72f9f37a1.tmp-daily.cvd' ...
Database test passed.
daily.cvd updated (version: 25820, sigs: 2462534, f-level: 63, builder: raynman)
main database available for download (remote version: 59)
Time: 26.4s, ETA: 0.0s [=============================>] 112.40MiB/112.40MiB       
Testing database: '/usr/local/share/clamav/tmp.3cc97/clamav-1197b7466f4ea80d0831b55e85bcab64.tmp-main.cvd' ...
Database test passed.
main.cvd updated (version: 59, sigs: 4564902, f-level: 60, builder: sigmgr)
bytecode database available for download (remote version: 331)
Time: 5.3s, ETA: 0.0s [=============================>] 289.44KiB/289.44KiB      
Testing database: '/usr/local/share/clamav/tmp.3cc97/clamav-759c07fa126e9bbd634636a0e7770777.tmp-bytecode.cvd' ...
Database test passed.
bytecode.cvd updated (version: 331, sigs: 94, f-level: 63, builder: anvilleg)

clamd (daemon)の起動

設定fileのcopy.

$ sudo cp ~/tmp/clamav-0.102.3/etc/clamd.conf.sample /usr/local/etc/clamd.conf

$ sudo vi /usr/local/etc/clamd.conf

※ freshclam.conf と同様、「Example」という記述があったので、コメント化
# Comment or remove the line below.
# Example

※clamd起動時に「Please define server type (local and/or TCP)」となる為、
  以下3行を非コメント化
FixStaleSocket yes
TCPSocket 3310
TCPAddr 127.0.0.1

自動起動 for systemd の設定

$ sudo cp ~/tmp/clamav-0.102.3/clamd/clamav-daemon.service /etc/systemd/system/
Created symlink /etc/systemd/system/multi-user.target.wants/clamav-daemon.service
  → /etc/systemd/system/clamav-daemon.service.
Created symlink /etc/systemd/system/sockets.target.wants/clamav-daemon.socket
  → /usr/lib/systemd/system/clamav-daemon.socket.

$ sudo systemctl start  clamav-daemon.service
$ sudo systemctl status clamav-daemon.service
● clamav-daemon.service - Clam AntiVirus userspace daemon
   Loaded: loaded (/etc/systemd/system/clamav-daemon.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2020-05-23 11:26:14 JST; 2s ago
     Docs: man:clamd(8)
           man:clamd.conf(5)
           https://www.clamav.net/documents/
 Main PID: 9398 (clamd)
    Tasks: 1 (limit: 24004)
   Memory: 182.4M
   CGroup: /system.slice/clamav-daemon.service
           └─9398 /usr/local/sbin/clamd --foreground=true

clamdscan によるvirus scan

www.eicar.org によるテストfileを利用します

$ wget http://www.eicar.org/download/eicar.com
$ wget http://www.eicar.org/download/eicar.com.txt
$ wget http://www.eicar.org/download/eicar_com.zip
$ wget http://www.eicar.org/download/eicarcom2.zip

## virusありの場合
$ /usr/local/bin/clamdscan -i eicar.com
/home/end0tknr/tmp/CLAM_AV/eicar.com: Win.Test.EICAR_HDB-1 FOUND

----------- SCAN SUMMARY -----------
Infected files: 1
Time: 0.002 sec (0 m 0 s)

## virusなしの場合
[end0tknr@cent80 CLAM_AV]$ /usr/local/bin/clamdscan -i foo.txt 

----------- SCAN SUMMARY -----------
Infected files: 0
Time: 0.001 sec (0 m 0 s)

## clamdが起動していない等、errorの場合
$ /usr/local/bin/clamdscan -i eicar.com
ERROR: Could not connect to clamd on 127.0.0.1: Connection refused

----------- SCAN SUMMARY -----------
Infected files: 0
Total errors: 1
Time: 0.000 sec (0 m 0 s)

IISへのSSL証明書登録には、linuxで使用する公開鍵(PEM)形式でなくPKCS#12(pfx)形式で

openssl による PKCS#12(pfx)形式への変換

秘密鍵も用意した上で、以下のopensslコマンドで変換できます。

$ openssl pkcs12 -export -inkey private.key -in ssl.crt -out ssl.pfx
Enter Export Password:
Verifying - Enter Export Password:

IISサーバへの登録手順

以下は、IISサーバでのSSL証明書更新の経験がない、自分用メモ

  • 「管理ツール」から「IISマネージャー」を開き、
  • 更に「サーバー証明書」画面から「インポート」ダイアログを開き、
  • SSL証明書(pfx形式)を指定

f:id:end0tknr:20200520151732p:plain

f:id:end0tknr:20200520151745p:plain

  • 「サイト」から「バインド」ダイアログを開き、
  • httpsの「編集」から、先程インポートしたSSL証明書を選択し完了

IIS再起動も不要

f:id:end0tknr:20200520151755p:plain

Google Maps Platform Directions API + python による道路距離計測

以下を参考に、python による http request の練習

cloud.google.com

qiita.com

で、以下の通り

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import json
import urllib
import urllib.request
import urllib.parse
import sys

conf = \
    {"req_url" : "https://maps.googleapis.com/maps/api/directions/json",
     "req_opts": {"mode": "driving",
                  "avoid":"tolls|highways"},
#                  "avoid":"tolls|highways|ferries"},
     "api_key" : "ないしょ"}
origins = ["北海道札幌市北区北6条西4丁目",       #札幌駅
           "宮城県仙台市青葉区中央一丁目",       #仙台駅
           "東京都渋谷区道玄坂一丁目",            #渋谷駅
       "愛知県名古屋市中村区名駅1丁目1−4",#名古屋駅
           "大阪市北区芝田一丁目1番2号",         #大阪梅田駅
           "広島県広島市南区松原町2番37号",   #広島駅
           "福岡県福岡市博多区博多駅中央街1−1",#博多駅
           ]


def main():

    pref_cities = load_destinations( sys.argv[1] )
    
    # 出力先fileのopen
    out_f = open("distances.txt", 'w', encoding='utf-8', errors='replace')
    
    for to_pref_city in pref_cities:
        print("\t".join(to_pref_city))
        distances = to_pref_city
        for from_address in origins:
            if len(to_pref_city)<2 or not to_pref_city[1]:
                distances.append("")
            else:
                distance = calc_road_distance(from_address,to_pref_city)
                distances.append(str(distance))
        print("\t".join(distances), file=out_f)
        
    out_f.close()


def load_destinations(destinations_file):

    pref_cities = []
    f = open(destinations_file, mode='r',encoding='utf-8')
    for line in f:
        pref_city = line.split()
        pref_cities.append(pref_city)
    f.close()

    return pref_cities

def calc_road_distance(from_address,to_pref_city):
            
    req_params = conf["req_opts"]
    req_params["origin"] = from_address
    req_params["destination"] =  to_pref_city[0]+""+to_pref_city[1]
    req_params_str = urllib.parse.urlencode(req_params)
    req_url_0 = conf["req_url"] +"?"+ req_params_str
    req_url   = req_url_0 + "&key=" + conf["api_key"]

    req = urllib.request.Request(req_url)
    try:
        res = urllib.request.urlopen(req)
        # requestsモジュールでは、接続と読込両方のtimeout設定可のよう
        # https://blog.cosnomi.com/posts/1259/
#    except (urllib.error.HTTPError, urllib.error.URLError) as err:
    except urllib.error.HTTPError as err:
        print(err.code, req_url_0)
        return None
    except urllib.error.URLError as err:
        print(err.reason, req_url_0)
        return None
    except:
        print("unknown error", req_url_0)
        return None

    content_str = res.read()
    res.close()
    
    content = json.loads(content_str)
    if content['status'] != "OK":
        return None

    # 複数の経路が得られる場合がある為、最短距離を返す
    distances = []
    for routes in content['routes']:
        for legs in routes['legs']:
            distances.append(legs['distance']['value'])
    distance = sorted(distances)
    return distance[0]

            
if __name__ == '__main__':
    main()

国交省 - 新型コロナウイルス感染症対策のため、暫定的な措置として、建築士法に基づく重要事項説明について、対面ではない、ITを活用した実施が可能となりました

「暫定」といっても、もとの「対面説明」に戻すのは難しいと思います

www.mlit.go.jp

social-app-django for pytho による google oauth ログイン (social login/auth)

https://nmomos.com/tips/2019/07/05/django-social-auth/

google の oauth を使用したログインを実現する為、 上記urlの google 関連部分を写経。

TODO

参考url

Step1: python仮想環境作成と auth-app-django の install や django project 作成

## 仮想環境 作成
$ cd ~/dev/
$ python3 -m venv venv_social_auth

## 仮想環境 有効化
$ cd venv_social_auth
$ source ./bin/activate

## django や social-auth-app-django の install
$ pip install django==2.2
$ pip install social-auth-app-django

## django project 作成.
## (通常、djangoでは config dir名 = project名となる為
##  一度、config の名前でproject を作成し、後でdir名を変更)
$ django-admin startproject config
$ mv config social_auth

※「python manage.py startapp ~」のように通常、project内に アプリを作成しますが、今回は極小規模ですので、これを行いません。

Step2: auth-app-django install 後の設定

$ vi config/setting.py
ALLOWED_HOSTS = ['*']                                              ## CHANGE

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'social_django',                                           ## ADD
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',

    'social_django.middleware.SocialAuthExceptionMiddleware',     ## ADD
]

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],            ## CHANGE
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',

                'social_django.context_processors.backends',       ## ADD
                'social_django.context_processors.login_redirect', ## ADD
            ],
        },
    },
]

AUTHENTICATION_BACKENDS = (                         ## ADD
    'social_core.backends.open_id.OpenIdAuth',      ## ADD
    'social_core.backends.google.GoogleOpenId',     ## ADD
    'social_core.backends.google.GoogleOAuth2',     ## ADD

    'social_core.backends.github.GithubOAuth2',     ## ADD
    'social_core.backends.twitter.TwitterOAuth',    ## ADD
    'social_core.backends.facebook.FacebookOAuth2', ## ADD

    'django.contrib.auth.backends.ModelBackend',    ## ADD
)                                                   ## ADD


LOGIN_URL          = 'login'                        ## ADD
LOGIN_REDIRECT_URL = 'home'                         ## ADD
$ vi config/urls.py

from django.conf.urls import include                ## ADD
from django.contrib.auth import views as auth_views ## ADD
from . import views                                 ## ADD

urlpatterns = [
    path('admin/', admin.site.urls),

    path('', views.index, name='index'),                               ## ADD
    path('login/', auth_views.LoginView.as_view(), name='login'),      ## ADD
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),   ## ADD
    path('oauth/', include('social_django.urls', namespace='social')), ## ADD
    path('accounts/profile/', views.index, name='index')               ## ADD
]

Step3: auth-app-django install 後の db migrate

social-app-django は migrate 情報を持っている為、 事前の「python manage.py makemigrations」実行は不要です

$ python manage.py migrate

Step4: Goolge Developers Console で クライアント ID/シークレット取得

以降では、Googleのproject作成後における認証情報設定等について記載します。

まず、画面左上にある「Google APIs」をクリックし、 画面左にメニューペインを表示

f:id:end0tknr:20200508083357p:plain

「認証情報」→「同意画面を構成」をクリック

f:id:end0tknr:20200508083407p:plain

「OAuth同意画面」の「User Type」で「外部」を選択

f:id:end0tknr:20200508083422p:plain

「OAuth同意画面」の「アプリケーション名」を入力。 (サポートメールは入力不要かもしれません)

f:id:end0tknr:20200508083431p:plain

「認証情報を作成」→「OAuthクライアントID」をクリック

f:id:end0tknr:20200508083442p:plain

「OAuthクライアントの作成」において 「アプリケーションの種類」「名前」「承認済のリダイレクトURL」を入力

f:id:end0tknr:20200508083451p:plain

※私の場合、VirturlBox内のcentos8で動作させていることもあり 「承認済のリダイレクトURL」には以下を入力しています。

以上で、クライアント ID とシークレットが発行される為、 これを config/setting.py へ登録

$ vi config/setting.py

SOCIAL_AUTH_GOOGLE_OAUTH2_KEY    = 'ないしょ' ## ADD
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'ないしょ' ## ADD

Step5: view や template の作成

$ vi config/views.py ## NEW FILE

from django.shortcuts import render

def index(request):
    return render(request, 'index.html')

htmlテンプレートは、以下のurlにあるものをそのままcopyしました。

https://github.com/mila411/django-social-auth

Step6: django の起動と動作確認

最後に以下でdjango を起動すれば、ブラウザで動作確認可能です。

$ python manage.py runserver 0.0.0.0:8080

django for python で、table "django_admin_log" violates foreign key エラー

IntegrityError at /admin/diary/diary/add/
insert or update on table "django_admin_log" violates
foreign key constraint "django_admin_log_user_id_c564eba6_fk_auth_user_id"
DETAIL:  Key (user_id)=(2) is not present in table "auth_user".

のようなエラーが発生。

https://stackoverflow.com/questions/15124523/integrity-error-on-django-admin-log-after-updating-existing-site-to-new-django-1?rq=1

migrate の漏れ? のような気もしますが、上記urlの通り、 drop table django_admin_log; と、改めての create table 等で解消。

まず、drop table。

sql> DROP TABLE django_admin_log;

次に、以下の sqlmigrate コマンドでsqlが標準出力されるので これを、sqlにて発行。

$ ./manage.py sqlmigrate admin 0001
CREATE TABLE "django_admin_log" (
  "id"          serial NOT NULL PRIMARY KEY,
  "action_time" timestamp with time zone NOT NULL,
  "object_id"   text NULL,
  "object_repr" varchar(200) NOT NULL,
  "action_flag" smallint NOT NULL CHECK ("action_flag" >= 0),
  "change_message" text NOT NULL,
  "content_type_id" integer NULL, "user_id" integer NOT NULL
);
ALTER TABLE "django_admin_log"
  ADD CONSTRAINT "django_admin_log_content_type_id_c4bce8eb_fk_django_co"
  FOREIGN KEY ("content_type_id")
  REFERENCES "django_content_type" ("id")
  DEFERRABLE INITIALLY DEFERRED;
  
ALTER TABLE "django_admin_log"
  ADD CONSTRAINT "django_admin_log_user_id_c564eba6_fk_accounts_customuser_id"
  FOREIGN KEY ("user_id")
  REFERENCES "accounts_customuser" ("id")
  DEFERRABLE INITIALLY DEFERRED;

CREATE INDEX "django_admin_log_content_type_id_c4bce8eb"
  ON "django_admin_log" ("content_type_id");
CREATE INDEX "django_admin_log_user_id_c564eba6"
  ON "django_admin_log" ("user_id");

perl で twitter api へ oauth 後、ユーザ情報を取得

https://blog.thingslabo.com/archives/000063.html

OAUTHの練習として、上記urlの写経。

ただし、上記urlは2011年の内容で、その後のtwitterの仕様変更により 動作しない部分がありましたので、修正しています。

以下を callback.pl というfile名で保存し、 これをCGIとしてブラウザからアクセスすると、ユーザ情報詳細?が表示されます。

OAUTHでの動作を確認する為、「print STDERR "POINT 1.1\n"」のような debug write を大量に入れています

#!/usr/local/bin/perl
use strict;
use warnings;
use utf8;
use CGI;
use OAuth::Lite::Consumer;
use LWP::UserAgent;
use JSON;
use Data::Dumper;

# 「CGI::param called in list context ...」のerrorとなる為、以下を記載
# https://end0tknr.hateblo.jp/entry/20150621/1434845779
$CGI::LIST_CONTEXT_WARN = 0;

# key や secretは、https://developer.twitter.com/en にて取得
my $OAUTH_CONSUMER_PARAM =
    {consumer_key      =>'ないしょ',
     consumer_secret   =>'ないしょ',
     
     site              =>"http://twitter.com/",
     request_token_path=>"https://api.twitter.com/oauth/request_token",
     access_token_path =>"https://api.twitter.com/oauth/access_token",
     authorize_path    =>"https://api.twitter.com/oauth/authorize",
     
     callback_url => 'http://cent80.a5.jp:8080/Test/callback.pl'};

# 今回、試用したAPIの内容は、以下のurlにある説明が分かりやすい。
#  refer to https://syncer.jp/twitter-api-matome/get/account/verify_credentials
my $API_REQ_URL = 'https://api.twitter.com/1.1/account/verify_credentials.json';


main();


sub main {

    print STDERR "POINT 1.1\n";
    
    my $consumer = OAuth::Lite::Consumer->new(%$OAUTH_CONSUMER_PARAM);
    my $query = CGI->new;

    print STDERR "POINT 1.2\n";

    my $oauth_token    = $query->param('oauth_token');
    my $oauth_verifier = $query->param('oauth_verifier');

    if($oauth_token and $oauth_verifier){
        print STDERR "POINT 1.3\n";
        return reqest_with_token_and_verifier($consumer,
                                              $oauth_token,
                                              $oauth_verifier);
    }
    
    print STDERR "POINT 1.4\n";
    return reqest_without_token_and_verifier($consumer,$query);
}

sub reqest_with_token_and_verifier {
    my ($consumer,$oauth_token,$oauth_verifier) = @_;

    print STDERR "POINT 2.1\n";
    my $access_token =
        $consumer->get_access_token(token   =>$oauth_token,
                                    verifier=>$oauth_verifier);
                                    
    print STDERR "POINT 2.2\n";
    my $req = $consumer->gen_oauth_request(method=> 'GET',
                                           url   => $API_REQ_URL,
                                           token => $access_token);
    
    print STDERR "POINT 2.3\n";
    
    my $ua = new LWP::UserAgent();
    my $res = $ua->request($req);

    if(not $res->is_success){
        print STDERR "POINT 2.4\n";
        my $tmp_msg = join(" ", $res->status_line,  $!);
        die $tmp_msg;
    }

        print STDERR "POINT 2.5\n";

    my $account = JSON::decode_json($res->content);

    print "Content-type: text/plain;charset=UTF-8\n\n";
    
    print Dumper($account);
    return;
}

sub reqest_without_token_and_verifier {
    my ($consumer,$query) = @_;

    print STDERR "POINT 3.1\n";
    my $request_token = $consumer->get_request_token();
    my $uri = $consumer->url_to_authorize(token => $request_token);
    print $query->redirect($uri);
    print STDERR "POINT 3.2\n";
    return;
}

ちなみに、以下が、最終的にtwitter apiから取得できた情報

$VAR1 = {
          'status' => {
                        'truncated' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
                        'possibly_sensitive' => $VAR1->{'status'}{'truncated'},
                        'in_reply_to_user_id' => undef,
                        'entities' => {
                                        'hashtags' => [
                                                        {
                                                          'indices' => [
                                                                         39,
                                                                         42
                                                                       ],
                                                          'text' => 'fb'
                                                        }
                                                      ],
                                        'user_mentions' => [],
                                        'symbols' => [],
                                        'urls' => [
                                                    {
                                                      'indices' => [
                                                                     16,
                                                                     38
                                                                   ],
                                                      'display_url' => "qiita.com/ine1127/items/\x{2026}",
                                                      'url' => 'http://t.co/VAlerC7r1q',
                                                      'expanded_url' => 'http://qiita.com/ine1127/items/64b5b6cf52471c3fe59c'
                                                    }
                                                  ]
                                      },
                        'created_at' => 'Sat Sep 12 22:57:03 +0000 2015',
                        'is_quote_status' => $VAR1->{'status'}{'truncated'},
                        'favorite_count' => 1,
                        'id_str' => '642834335138099200',
                        'place' => undef,
                        'lang' => 'ja',
                        'in_reply_to_user_id_str' => undef,
                        'in_reply_to_status_id' => undef,
                        'in_reply_to_screen_name' => undef,
                        'text' => "\x{554f}\x{984c}\x{306a}\x{3051}\x{308c}\x{3070}\x{3001}\x{3044}\x{3044}\x{3093}\x{3067}\x{3059}\x{3051}\x{3069}\x{306d}
http://t.co/VAlerC7r1q #fb",
                        'contributors' => undef,
                        'favorited' => $VAR1->{'status'}{'truncated'},
                        'geo' => undef,
                        'id' => '642834335138099200',
                        'in_reply_to_status_id_str' => undef,
                        'retweet_count' => 0,
                        'coordinates' => undef,
                        'source' => '<a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>',
                        'retweeted' => $VAR1->{'status'}{'truncated'}
                      },
          'url' => 'http://t.co/RL6kuGrhX7',
          'created_at' => 'Sat Jul 18 08:56:42 +0000 2009',
          'follow_request_sent' => $VAR1->{'status'}{'truncated'},
          'profile_image_url_https' => 'https://pbs.twimg.com/profile_images/1881562143/profile_normal.gif',
          'name' => 'end0tknr',
          'lang' => undef,
          'translator_type' => 'none',
          'id_str' => '57893029',
          'following' => $VAR1->{'status'}{'truncated'},
          'protected' => $VAR1->{'status'}{'truncated'},
          'profile_background_tile' => $VAR1->{'status'}{'truncated'},
          'entities' => {
                          'description' => {
                                             'urls' => []
                                           },
                          'url' => {
                                     'urls' => [
                                                 {
                                                   'expanded_url' => 'http://d.hatena.ne.jp/end0tknr/',
                                                   'display_url' => 'd.hatena.ne.jp/end0tknr/',
                                                   'url' => 'http://t.co/RL6kuGrhX7',
                                                   'indices' => [
                                                                  0,
                                                                  22
                                                                ]
                                                 }
                                               ]
                                   }
                        },
          'statuses_count' => 520,
          'contributors_enabled' => $VAR1->{'status'}{'truncated'},
          'profile_image_url' => 'http://pbs.twimg.com/profile_images/1881562143/profile_normal.gif',
          'profile_text_color' => '333333',
          'description' => "\x{592a}\x{5bb0}\x{5e9c}\x{5929}\x{6e80}\x{5bae}\x{306e}\x{72db}\x{72ac}\x{306f}\x{3001}\x{5999}\x{306b}\x{304b}\x{308f}\x{3044}\x{3044}",
          'profile_sidebar_fill_color' => 'DDEEF6',
          'default_profile_image' => $VAR1->{'status'}{'truncated'},
          'profile_link_color' => '1DA1F2',
          'id' => 57893029,
          'default_profile' => bless( do{\(my $o = 1)}, 'JSON::PP::Boolean' ),
          'listed_count' => 1,
          'suspended' => $VAR1->{'status'}{'truncated'},
          'is_translator' => $VAR1->{'status'}{'truncated'},
          'profile_background_image_url' => 'http://abs.twimg.com/images/themes/theme1/bg.png',
          'geo_enabled' => $VAR1->{'status'}{'truncated'},
          'notifications' => $VAR1->{'status'}{'truncated'},
          'profile_use_background_image' => $VAR1->{'default_profile'},
          'followers_count' => 3,
          'friends_count' => 10,
          'profile_background_color' => 'C0DEED',
          'has_extended_profile' => $VAR1->{'status'}{'truncated'},
          'screen_name' => 'end0tknr',
          'location' => '',
          'needs_phone_verification' => $VAR1->{'status'}{'truncated'},
          'is_translation_enabled' => $VAR1->{'status'}{'truncated'},
          'profile_sidebar_border_color' => 'C0DEED',
          'favourites_count' => 0,
          'time_zone' => undef,
          'profile_background_image_url_https' => 'https://abs.twimg.com/images/themes/theme1/bg.png',
          'utc_offset' => undef,
          'verified' => $VAR1->{'status'}{'truncated'}
        };

PhpMetrics による php source の 循環複雑度計測( Cyclomatic complexity code metrics)

以下に、installや実行方法を記載しています。

https://github.com/phpmetrics/PhpMetrics

php の code metrics は、sonar qube でも可能ですが、 sonar qube の実行には、mysql等の準備もある為、PhpMetrics の方がお手軽な印象。

Step1 install Composer

phpのパッケージ管理システムである Composer を install します。

に記載のように以下の通り、実行すれば、OK

$ curl https://getcomposer.org/installer | /usr/local/bin/php
$ sudo mv composer.phar /usr/local/bin/composer

Step2 install PhpMetrics

以下により、PhpMetrics が local へ installされます

$ composer require phpmetrics/phpmetrics --dev
$ ls -lF
   71 May  3 02:41 composer.json
 4519 May  3 02:41 composer.lock
   84 May  3 02:41 vendor/

Step3 run PhpMetrics

以下のように実行することで、 ~/path/to/php_sources 以下にある php source の code metrics が、 myreport 以下に html 形式で生成されます。

$ /usr/local/bin/php \
  ./vendor/bin/phpmetrics \
  --report-html=myreport \
  ~/path/to/php_sources

LOC
    Lines of code                               27958
    Logical lines of code                       22461
    Comment lines of code                       5511
    Average volume                              1920.57
    Average comment weight                      22.07
    Average intelligent content                 22.07
    Logical lines of code by class              147
    Logical lines of code by method             17

Object oriented programming
    Classes                                     153
    Interface                                   0
    Methods                                     1300
    Methods by class                            8.5
    Lack of cohesion of methods                 2.61
    
Coupling
    Average afferent coupling                   1.41
    Average efferent coupling                   1.56
    Average instability                         0.75
    Depth of Inheritance Tree                   1.51
    
Package
    Packages                                    3
    Average classes per package                 55
    Average distance                            0.24
    Average incoming class dependencies         0.67
    Average outgoing class dependencies         1.67
    Average incoming package dependencies       0.67
    Average outgoing package dependencies       0.67

Complexity
    Average Cyclomatic complexity by class      30.23
    Average Weighted method count by class      37.98
    Average Relative system complexity          100.2
    Average Difficulty                          15.35
    
Bugs
    Average bugs by class                       0.64
    Average defects by class (Kan)              1.89

Violations
    Critical                                    0
    Error                                       97
    Warning                                     62
    Information                                 30
Done

re: Google日本語入力APIを利用したメンテナンスフリーな住所補完機能 ( zip2addr )

確かに、郵便番号→住所変換であれば、Google日本語入力API は有効ですね。

ただ、これ以外の Google日本語入力API 活用法を思い浮かびません...

以下は、 http://webtech-walker.com/ による zip2address.js の src。 (jsonpの実装方法を忘れたときの為のメモです)

/*
 * zip2address.js
 *
 * Copyright (c) 2010 Kazuhito Hokamura
 * Licensed under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * @author   Kazuhito Hokamura (http://webtech-walker.com/)
 * @version  0.0.1
 *
 * This script inspired by jQuery.zip2addr. (https://github.com/kotarok/jQuery.zip2addr)
 * Thank you for kotarok.
 *
 */

(function(window) {

    var d = document;
    var api_url = '//www.google.com/transliterate?langpair=ja-Hira|ja';
    var prefs = [
        '北海道', '青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県',
        '茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県',
        '新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県', '岐阜県',
        '静岡県', '愛知県', '三重県', '滋賀県', '京都府', '大阪府', '兵庫県',
        '奈良県', '和歌山県', '鳥取県', '島根県', '岡山県', '広島県', '山口県',
        '徳島県', '香川県', '愛媛県', '高知県', '福岡県', '佐賀県', '長崎県',
        '熊本県', '大分県', '宮崎県', '鹿児島県', '沖縄県'
    ];

    var zip2address = function(zip, callback) {
        var jsonp_callback = 'zip2address_jsonp' + (new Date()).getTime();
        var url = api_url + '&jsonp=' + jsonp_callback;
        var head = d.getElementsByTagName('head')[0];
        var script = d.createElement('script');
        
        // jsonp callback function
        window[ jsonp_callback ] = function(data) {
            var address = {};
            address.all = data[0][1][0];
            
            // check match pref
            for (var i = 0, l = prefs.length; i < l; i++) {
                var pref = prefs[i];
                if (address.all.indexOf(pref) === 0) {
                    address.pref = pref;
                    address.city = address.all.replace(pref, '');
                    break;
                }
            }
            
            // no match address
            if (!address.pref && !address.city) {
                address = undefined;
            }
            
            // callback function
            callback(address);
            
            // cleaning
            try {
                delete window[ jsonp_callback ];
            }
            catch (e) {}
            head.removeChild(script);
        };
        
        // check zip formtting
        if (/^\d{7}$/.test(zip)) {
            zip = zip.toString().replace(/(\d{3})(\d{4})/, '$1-$2');
        }
        else if (!/^\d{3}-\d{4}$/.test(zip)) {
            callback(undefined);
        }
        
        // call api by jsonp
        url += '&text=' + encodeURIComponent(zip);
        script.setAttribute('src', url);
        head.appendChild(script);
    };
    
    // export function
    window.zip2address = zip2address;
    
})(window);