flask 파일업로드 검증하기

파일 업로드의 경우 주의할 점이(업로드를 당하는 입장에서) 악성파일 혹은 원치 않는 파일들이 들어올 수 있음을 항상 인지해야한다. 그래서 왠만하면 파일 업로드를 잘 안만들거나 내부에서 몇명의 인가된 사용자만 따로 업로드 메뉴를 보이게 해서 제공하거나 하는 식으로 하곤 했었다. 그래도 업로드가 필요할 경우 아래와 같은 방법으로 제한 할 수 있다. 파일 확장자 체크 사이즈 체크 mime type 체크 그렇지만, 파일 확장자는 언제나 변경가능하기 때문에 위험하다. 예를 들어, 악성 자바스크립트, 웹쉘 같은 파일들을 .png, .jpg, .docx 등과 같이 업무에서 자주 사용하는 파일 확장자로 변경해서 올릴 여지가 여전히 있다. mime type 체크 역시 웹프레임워크 상에서 내려주는 값을 보면 문제가 생긴다. http://flask.pocoo.org/docs/1.0/patterns/fileuploads/ Flask 의 문서에서는 파일 사이즈에 대한 제한과 확장자에 대한 제한 그리고 secure_filename() 함수를 통한 서버내에서의 다른 경로로의 접근을 제한하고 있다. 위의 링크에 있는 소스를 가지고 Whale.exe 파일을 Whale.pdf 로 변경한 후에 파일 업로드를 올리고 아래와 같은 코드로 mimetype 을 찍어봤다. @app.route('/upload', methods=['POST']) def

flask-mqtt : subscribe 시 qos 설정 이슈 수정하기

이번 프로젝트를 하면서 주문관련 부분을 기존의 폴링(polling) 을 하던 방식에서 중간에 브로커서버를 두고 주문하는 쪽에서 주문을 보내면 브로커 서버의 특정 topic 을 구독하고 있는 구독자가 해당 주문을 받는 형태로 구성을 했다. 그 과정에서 mqtt 를 이용했고, emqtt 브로커 서버를 선택해서 사용하고 있다. 주문을 하는 쪽에서는 flask 로 구성되어 있어서 별도의 paho client 라이브러리를 띄워서 쓰기 보다는 flask-mqtt 를 사용했다.(내부적으로 paho 를 사용한다.) 문제발견 사용하다보니 자연스럽게 QOS 에 대한

jinja - {% break %} 사용하기

jinja template 는 강력한 형태의 for 문을 제공하는데, 당연히 일반적인 for 문 안에서의 continue 나 break 등이 가능할 것이라고 생각하지만, 기본적으로 제공하지는 않는다. 이런 기능을 사요하기 위해서는 jinja2.ext.loopcontrols 을 설치해야 한다. flask 에서 설치 하는 방법은 아래와 같이 app 객체내에서 jinja_env.add_extension 함수에 추가해 주면 된다. app.jinja_env.add_extension('jinja2.ext.loopcontrols') {% for sub_menu in sub_menu_list %} {% if sub_menu.code in permission %} <li class="on"> {{ sub_menu.menu }} </li> {% break %} {% endif %} 위의 코드는 메뉴의 코드가 권한에 있으면 메뉴를 표시하는 코드인데, 한번만 확인하면 빠져나오도록 {% break %} 문을 통해서 제어하고 있다. jinja 에서는 다양한 extension 을 제공하고 있는데, http://jinja.pocoo.org/docs/2.10/extensions/ 여기에 가면 그런 항목을 확인 할 수가 있다.

beaker_session MySQL server has gone away

매일 아침 와서 현재 개발중인 백오피스를 켜면 Internal Server Error 발생. 로그를 보니 아래와 같은 오류 발생 OperationalError: (pymysql.err.OperationalError) (2006, "MySQL server has gone away (error(32, 'Broken pipe'))") [SQL: u'SELECT beaker_cache.data \\nFROM beaker_cache \\nWHERE beaker_cache.namespace = %(namespace_1)s'] [parameters: {u'namespace_1': '17b85df148204386870d2de3b3beaf40'}] beaker_session github issue 로 찾아보니 떡하니 클로징된 이슈가 있다. https://github.com/bbangert/beaker/issues/126 cache 를 쓰지 않기 때문에 session.sa.pool_recycle 을 아래와 같이 추가해주었다. 그리고 나서 다음날 다시 같은 현상이 발생하는지 체크했는데, 발생하지 않았다. session_opts = { 'session.type': 'ext:database', 'session.url': app.config['SQLALCHEMY_DATABASE_URI'], 'session.cookie_expires': True, 'session.timeout': 600, 'session.sa.pool_recycle': 250 } app.wsgi_app = SessionMid

flask-babel 로 다국어 대응하기

flask 로 다국어 서비스를 만드는 일을 하고 있는데 일단 생각해 볼 부분이 API 와 WEB 이다. API는 모바일에서 요청이 들어오는 형태인데, 이 경우 모바일의 사용자 언어 설정을 파라미터로 받고 API 레벨에서 DB 내에 다국어 컬럼 혹은 행이 있다는 가정하에 select 를 해서 response 를 내보내면 된다. WEB 의 경우 AcceptLanguage 를 이용할 수도 있고, GET 요청의 파라미터로 언어코드가 전달 된다면 그것을 활용할 수도 있다. 일단 여기서는 WEB 상에서 언어별로 사전을 만들고 jinja 템플릿 안에서 어떻게 언어코드 별로 다른 문자를 보여줄것인지에 대해서 설명한다. 설치하기 pip install flask-babel b

flask-sqlalchemy multiple databases

사용하기 flask-sqlalchemy 에서 기본적으로 SQLALCHEMY_DATABASE_URI 설정을 통해서 mapping class 에 정의된 테이블들이 DB와 연결되게 된다. 그런데 1개 이상의 DB와 연결해야하는 경우가 생긴다. 기존의 회원정보를 같이 쓰는 다른 서비스의 경우가 대표적인 케이스이다. 이럴경우, flask-sqlalchemy 에서는 SQLALCHEMY_BINDS 를 통해서 여러 데이터베이스 URI를 지정할 수 있게 해주고 있다. app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:3306/test1' app.config['SQLALCHEMY_BINDS'] = { 'test2': 'mysql+pymysql://root:root@localhost:3306/test2', 'test1': 'mysql+pymysql://root:root@localhost:3306/test1' } 위의 설정을 보면, SQLALCHEMY_DATABASE_URI 를 지정하면서 SQLALCHEMY_BINDS 를 지정하고 있다. SQLALCHEMY_BINDS 는 키-값 형태로 지정하면 되는데, test1, test2 와 같이 지정을 해두었다. SQLALCHEMY_DATABASE_URI 에 지정한 값은 디폴트 값으로 지정되게 된다. class User(db.Model): __tablename__ = "tb_user" __bind_key__ = 'test2' id =

flask - request.script_root 이용하기

하나의 웹을 여러 path 에 올려야 할 경우가 있다. 예를 들면, 테스트를 위해서 /web1, /web2 이런식으로 구성해서 올릴 경우이다. 이런 경우 프론트단에서 URL로 지정해 놓은 값들을 수정해 줘야 한다. ajax 를 호출한다고 생각해 보자. 원래 /web/api/room 이런식으로 ajax 를 호출하는 경우가 있었다면 /web1, /web2 에 따라서 /web1/api/room, /web2/api/room 이렇게 변경해 줘야 한다. $.ajax({ url: "/web/api/room", type: "POST" }); 매번 고쳐주는 일은 번거롭다. 가장 간단하게 생각할 수 있는건 js 상에서 BASE_URL 변수를 두고 사용사는 시점에 변경하도록 하는 것이다. 이게 이상적으로 보일 수 있겠지만, 수정 포인트가 여러 곳에서 한곳으로 줄여졌다는 점외에 여전히 옮길 때 마다 수정해줘야 한다는 점은 변하지가 않았다. 또 한가지 방법은 flask 상에서 render_template 통해서 base_url 을 전달하고 그것을 아래와 같이 js 의 BASE_URL 변수에 전달해서 사용하는 방법이다. 이 방식은 수정할 필요가 없다는 장점은 있지만, html 과 js 가 분리된 경우에는 적용하기가 어렵다. var BASE_URL = "{{ base_url }}"; Flask 에서는

flask - render_template 어떻게 사용할까?

flask 를 사용하다보면 view 코드가 길어지는 경우가 있는데 그 중 하나가 views.py 에 route 함수가 많아져서 길어지는것 그리고 다른 하나는 render_template() 함수에 파라미터가 많아져서 길어지는 경우다. 전자의 경우 blueprint 로 분할하거나 resource 단위로 분할하면 해결할수 있다. (이건 다른 포스트에서 자세히 설명하겠다.) 후자의 경우에는 어떻게 해결 할수 있을까? 뭐 이런식으로 길어지는 경우다. return render_template('web.html', title="테스트페이지", color=template['COLOR'], ciurl=template['CIURL'],

flask - json_encoder 지정하기

API 상에서 JSON 으로 응답을 내보내기 위해서는 데이터를 Json Serialize(직렬화)를 해야한다. 그런데 json 에서 표현할 수 있는 데이터는 한정이 되어 있어서 각 프로그래밍 언어에 있는 모든 타입을 지원하지는 못한다. Decimal 형도 그런 예 중 하나인데 파이썬에서는 json.dumps()를 통해서 주로 직렬화를 하고 그 함수의 기능 중에 cls 인자를 통해서 JSONEncoder 를 구현한 서브 클래스를 지정해 주면 해당 인코더로 json 문자열을 만든다. flask 에서도 json.dumps 를 사용해서 json 문자열을 응답으로 내보낼 수도 있지만, jsonify() 를 이미 제공하고 있다. json 을 내보낼때 결국 어떤 객체에서 데이터를 가져오게 된다. 객체 내 형을 변환하거나 추가적으로 같은 내용의 다른 형의 변수를 두는 것도 한 방법이지만, 낭비라는 생각이 들었다. 또한 이런 클래스가 많은 경우 그런 클래스 각각 혹은 몇개의 상위 클래스에 to_json을 만들어서 따로 직렬화를 위한 작업을 해줄 수도 있겠지만, flask 에서는 app.json_encoder 을 제공하고 있어서 jsonify() 를 통해서 응답을 보낼 경우, 지정한 인코더를 통해서 생성되도록 할 수 있다. 예를 들어, 아래의 CustomJsonEncoder 는 Decimal 형을 문자열로 치환해서 보내주고 있고, None 인 값에 대해서는 빈 문자열로 치환해서 보내주고 있다. JsonEncoder 를 활용하면, 타입에 따라서 json 에서 표시할 때만 다르게 표시하는 것이

Security bugs on Windows servers: Flask 0.12.2 and Werkzeug 0.12.2 released

원문 : https://www.palletsprojects.com/blog/flask-werkzeug-0122-security-release/ Flask 0.12.2, Werkzeug 0.12.2 가 릴리즈 되었는데 이것들은 safe_join 함수에 대한 보안관련 버그수정을 포함하고 있다. 이 문제는 Windows 서버에서 application 을 운영시에 발생한다. Details David Lord가 이 버그를 발견했고, 개인 이메일로 다른 관리자에게 알렸다: While going through PR #2059 about safe_join, I looked up Python's ntpath.join and discovered a vulnerability that safe_join on Windows doesn't cover. https://docs.python.org/3/library/os.path.html#os.path.join: "os.path.join("c:", "foo") represents a path relative to the current directory on drive C: (c:foo)" safe_join('\\root\\path', 'd:', 'test.txt') would break out of