Show powershell errors in flask-wtf web form

hi all,

so im really getting along with my flask-wtf form and im nearing its completion

now i want to show if powershell errors, i want it to show on the web form

this is my py script

from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, DecimalField, RadioField, SelectField, TextAreaField, FileField, SubmitField
from wtforms.validators import InputRequired, Length, DataRequired, EqualTo, Regexp, ValidationError
import re
import subprocess

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secretkey'


class PasswordForm(FlaskForm):
    un = StringField('Username', [InputRequired(message='please enter your Username')])
    op = PasswordField('Current Password', [InputRequired(message='please enter your current password')])
    np = PasswordField('New Password', [InputRequired(message='please enter your new password')])
    cnp = PasswordField('Confirm New Password')
    dom = SelectField('Domain', choices=[('lon-prod-dc01.domain.com', 'prod'), ('lon-corp-dc01.domain.com', 'corp'), ('dc01.robo84.net', 'robo84')])
    
    def validate_un(form, field):
        if not field.data == form.un.data.lower():
            raise ValidationError('Username needs to be Lowercase')
    
    def validate_np(form, field):
        if form.un.data:
            if any (name in field.data.lower() for name in form.un.data.split(".")):
                raise ValidationError('New password cant contain firstname or lastname')
        if field.data.lower() == form.op.data.lower():
            raise ValidationError('New password cant match Current password')
        if len(field.data) < 12:
            raise ValidationError('New password must be at least 12 characters')
        if not re.search(r"[0-9]", field.data):
            raise ValidationError('New password has to contain one number')
        if not re.search(r"[a-z]", field.data):
            raise ValidationError('New password has to contain one lower case character')
        if not re.search(r"[A-Z]", field.data):
            raise ValidationError('New password has to contain one upper case character')
        if not re.search(r"[\`\¬\!\"\£\$\%\^\&\*\(\)\-\_\=\+\\\|\[\]\;\'\#\,\.\/\{\}\:\@\~\<\>\?]", field.data):
            raise ValidationError('New password has to contain one special character')
        if not field.data == form.cnp.data:
            raise ValidationError('New password has to match Confirm new password')
        
    
@app.route('/password', methods=['GET', 'POST'])
def password():
    form = PasswordForm()
    if request.method == 'POST' and form.validate():
        subprocess.run(f'powershell.exe $cred = Import-CliXml -Path C:\\python\\cred.xml; Set-ADAccountPassword -Credential $cred -Identity {form.un.data} -OldPassword (ConvertTo-SecureString -AsPlainText {form.op.data} -Force) -NewPassword (ConvertTo-SecureString -AsPlainText {form.cnp.data} -Force) -Server {form.dom.data}', shell=True)
        return '<h1>The username is {}. The old password is {}. the new password is {}. changing for domain {}'.format(form.un.data, form.op.data, form.cnp.data, form.dom.data)
    return render_template('password.html', form=form)

if __name__ == '__main__':
    app.run(debug=True)

my html script

{% block content %}
    <h1>Change Password</h1>
    <form method="post" novalidate>
		{{form.hidden_tag()}}
		
		<p>{{ form.un.label }}</p>
		<p>{{ form.un}}</p>
		
		{% if form.un.errors %}
		<ul>
			{% for error in form.un.errors %}
			<li>
				{{error}}
			</li>
			{% endfor %}
		</ul>
		{% endif %}
		
		<p>{{ form.op.label }}</p>
		<p>{{ form.op}}</p>
		
		{% if form.op.errors %}
		<ul>
			{% for error in form.op.errors %}
			<li>
				{{error}}
			</li>
			{% endfor %}
		</ul>
		{% endif %}
		
		<p>{{ form.np.label }}</p>
		<p>{{ form.np}}</p>
		
		{% if form.np.errors %}
		<ul>
			{% for error in form.np.errors %}
			<li>
				{{error}}
			</li>
			{% endfor %}
		</ul>
		{% endif %}
		
		<p>{{ form.cnp.label }}</p>
		<p>{{ form.cnp}}</p>
		
		{% if form.cnp.errors %}
		<ul>
			{% for error in form.cnp.errors %}
			<li>
				{{error}}
			</li>
			{% endfor %}
		</ul>
		{% endif %}
		
		<p>{{ form.dom.label }}</p>
		<p>{{ form.dom}}</p>
		
		<input type="submit" value="Submit">
    </form>
{%endblock%}

it works well but only problem is if they enter in there old password wrong ie op there not warned about it, it only shows in the cmd part but obviously the end user cant see this and can just see the html form

thanks,
rob

In subprocess.run() you need to capture stdout and stderr and put it into the html output appropriately. See the subprocess.run docs for how to do this.

1 Like

can i output the error in the same html script?

i want it not to run the subprocess.run command if it errors

ok im doing this

@app.route('/password', methods=['GET', 'POST'])
def password():
    form = PasswordForm()
    if request.method == 'POST' and form.validate():
        try:
            result = subprocess.run(f'powershell.exe $cred = Import-CliXml -Path C:\\python\\cred.xml; Set-ADAccountPassword -Credential $cred -Identity {form.un.data} -OldPassword (ConvertTo-SecureString -AsPlainText {form.op.data} -Force) -NewPassword (ConvertTo-SecureString -AsPlainText {form.cnp.data} -Force) -Server {form.dom.data}', capture_output=True, text=True, shell=False)
            print("STDOUT:", result.stdout)
            print("STDERR:", result.stderr)
        except:
            print("something went wrong")
        return '<h1>The username is {}. The old password is {}. the new password is {}. changing for domain {}'.format(form.un.data, form.op.data, form.cnp.data, form.dom.data)
    return render_template('password.html', form=form)

if __name__ == '__main__':
    app.run(debug=True)

but when the subprocess command fails its not showing the print message in except

also if the sub process command fails i dont want it to display that return line ie to show what you typed in username old password, new password, domain

ok got it working, now in cmd i see the print error, now got to figure out how to show the error in html, any takers

try:
            result = subprocess.run(f'powershell.exe $cred = Import-CliXml -Path C:\\python\\cred.xml; Set-ADAccountPassword -Credential $cred -Identity {form.un.data} -OldPassword (ConvertTo-SecureString -AsPlainText {form.op.data} -Force) -NewPassword (ConvertTo-SecureString -AsPlainText {form.cnp.data} -Force) -Server {form.dom.data}', check=True, capture_output=True, text=True, shell=False)
            print("STDOUT:", result.stdout)
            print("STDERR:", result.stderr)
            return '<h1>The username is {}. The old password is {}. the new password is {}. changing for domain {}'.format(form.un.data, form.op.data, form.cnp.data, form.dom.data)
        except:
            print("something went wrong")
    return render_template('password.html', form=form)

alright done

try:
            result = subprocess.run(f'powershell.exe $cred = Import-CliXml -Path C:\\python\\cred.xml; Set-ADAccountPassword -Credential $cred -Identity {form.un.data} -OldPassword (ConvertTo-SecureString -AsPlainText {form.op.data} -Force) -NewPassword (ConvertTo-SecureString -AsPlainText {form.cnp.data} -Force) -Server {form.dom.data}', check=True, capture_output=True, text=True, shell=False)
            return '<h1>The username is {}. The old password is {}. the new password is {}. changing for domain {}'.format(form.un.data, form.op.data, form.cnp.data, form.dom.data)
        except:
            return 'Something went wrong, Either current password was incorrect or re-using old password history for new password is not allowed, please try again'
    return render_template('password.html', form=form)

but now the stderror could be two things, either
you have enbtered in old password incorrect, or
you cant re-use an old existing password for your new password

so how do i return different errors ie show on the web page depending on what the stderr is

Look for the error messages you know about and produce a suitable page is a typical method. Male sure you handle unexpected error messages.

would this be an if/else or try/except?

You will use “if” to test for the messages you know about.

if “error message” in stderr_string:
     # handle error message condition
1 Like

ok i stand corrected the stderr is the same so i think it wont work regardless as the error for anything ie im entering in my old password incorrectly new password cant match previous old passwords is all the same error

The password does not meet the length, complexity, or history requirement of the domain

if request.method == 'POST' and form.validate():
            result = subprocess.run(f'powershell.exe $cred = Import-CliXml -Path C:\\python\\cred.xml; Set-ADAccountPassword -Credential $cred -Identity {form.un.data} -OldPassword (ConvertTo-SecureString -AsPlainText {form.op.data} -Force) -NewPassword (ConvertTo-SecureString -AsPlainText {form.cnp.data} -Force) -Server {form.dom.data}', capture_output=True, text=True, shell=False)
            return '<h1>The username is {}. The old password is {}. the new password is {}. changing for domain {}'.format(form.un.data, form.op.data, form.cnp.data, form.dom.data)
        if "The specified network password is not correct" in stderr_string():
        return 'your old/current password is incorrect, please click back and try again'

what am i doing wrong here Barry please?

ok this works guys, can you please check and let me know

if request.method == 'POST' and form.validate():
        result = subprocess.run(f'powershell.exe $cred = Import-CliXml -Path C:\\python\\cred.xml; Set-ADAccountPassword -Credential $cred -Identity {form.un.data} -OldPassword (ConvertTo-SecureString -AsPlainText {form.op.data} -Force) -NewPassword (ConvertTo-SecureString -AsPlainText {form.cnp.data} -Force) -Server {form.dom.data}', capture_output=True, text=True, shell=False)
        if 'The specified network password is not correct' in result.stderr:
            return 'your old/current password is incorrect, please click back and try again'
        if 'The password does not meet the length, complexity, or history requirement of the domain' in result.stderr:
            return 'cant re-use one of your old passwords or cant change password as still less than one day old'
        if result.returncode == 0:
            return '<h1>The username is {}. The old password is {}. the new password is {}. changing for domain {}'.format(form.un.data, form.op.data, form.cnp.data, form.dom.data)
    return render_template('password.html', form=form)

What happens if someone puts a space in their password? Are you going to pass it along to the command and let people put whatever parameters they like on the command?

ok done, ive added that to my for validation, i have many

def validate_np(form, field):
        if form.un.data:
            if any (name in field.data.lower() for name in form.un.data.split(".")):
                raise ValidationError('New password cant contain firstname or lastname')
        if field.data.lower() == form.op.data.lower():
            raise ValidationError('New password cant match Current password')
        if re.search(r"\s", field.data):
            raise ValidationError('New password cant contain any spaces')
        if not re.search(r"[0-9]", field.data):
            raise ValidationError('New password has to contain one number')
        if not re.search(r"[a-z]", field.data):
            raise ValidationError('New password has to contain one lower case character')
        if not re.search(r"[A-Z]", field.data):
            raise ValidationError('New password has to contain one upper case character')
        if not re.search(r"[\`\¬\!\"\£\$\%\^\&\*\(\)\-\_\=\+\\\|\[\]\;\'\#\,\.\/\{\}\:\@\~\<\>\?]", field.data):
            raise ValidationError('New password has to contain one special character')
        if len(field.data) < 12:
            raise ValidationError('New password must be at least 12 characters')
        if not field.data == form.cnp.data:
            raise ValidationError('New password has to match Confirm new password')

This is an utter pain for the user, but also, it’s going to cause you problems. Just like spaces will.

The solution isn’t to make things even worse for the user. I was trying to hint at proper ways of passing strings as parameters. Look into how the command would accept a password with a space in it, and do that. Never force the user to fix their data due to limitations in your program.

While you’re at it, remove all of these annoying restrictions, and allow XKCD 936 passwords. If they’re long enough, they’re secure. All these hassles are actually pointless, plus they tend to make people write their passwords down. Don’t do it.